diff options
155 files changed, 10316 insertions, 4997 deletions
diff --git a/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBean.java b/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBean.java index 28528ec83c..1bc9c6ea6e 100644 --- a/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBean.java +++ b/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBean.java @@ -19,8 +19,17 @@ */ package org.apache.qpid.server.store.berkeleydb.jmx; +import static org.apache.qpid.server.model.ReplicationNode.COALESCING_SYNC; +import static org.apache.qpid.server.model.ReplicationNode.DESIGNATED_PRIMARY; +import static org.apache.qpid.server.model.ReplicationNode.DURABILITY; +import static org.apache.qpid.server.model.ReplicationNode.GROUP_NAME; +import static org.apache.qpid.server.model.ReplicationNode.HELPER_HOST_PORT; +import static org.apache.qpid.server.model.ReplicationNode.HOST_PORT; +import static org.apache.qpid.server.model.ReplicationNode.ROLE; + import java.io.IOException; -import java.util.List; +import java.util.Collection; +import java.util.HashMap; import java.util.Map; import javax.management.JMException; @@ -36,19 +45,16 @@ import javax.management.openmbean.TabularDataSupport; import javax.management.openmbean.TabularType; import org.apache.log4j.Logger; -import org.apache.qpid.AMQStoreException; import org.apache.qpid.server.jmx.AMQManagedObject; import org.apache.qpid.server.jmx.ManagedObject; -import org.apache.qpid.server.store.berkeleydb.BDBHAMessageStore; +import org.apache.qpid.server.model.ConfiguredObjectFinder; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; /** * Management mbean for BDB HA. - * <p> - * At runtime, the classloader loading this clas must have visibility of the other Qpid JMX classes. This is - * currently arranged through OSGI using the <b>fragment</b> feature so that this bundle shares the - * same classloader as broker-plugins-management-jmx. See the <b>Fragment-Host:</b> header within the MANIFEST.MF - * of this bundle. - * </p> */ public class BDBHAMessageStoreManagerMBean extends AMQManagedObject implements ManagedBDBHAMessageStore { @@ -58,12 +64,13 @@ public class BDBHAMessageStoreManagerMBean extends AMQManagedObject implements M private static final CompositeType GROUP_MEMBER_ROW; private static final OpenType<?>[] GROUP_MEMBER_ATTRIBUTE_TYPES; + static { try { GROUP_MEMBER_ATTRIBUTE_TYPES = new OpenType<?>[] {SimpleType.STRING, SimpleType.STRING}; - final String[] itemNames = new String[] {BDBHAMessageStore.GRP_MEM_COL_NODE_NAME, BDBHAMessageStore.GRP_MEM_COL_NODE_HOST_PORT}; + final String[] itemNames = new String[] {GRP_MEM_COL_NODE_NAME, GRP_MEM_COL_NODE_HOST_PORT}; final String[] itemDescriptions = new String[] {"Unique node name", "Node host / port "}; GROUP_MEMBER_ROW = new CompositeType("GroupMember", "Replication group member", itemNames, @@ -71,7 +78,7 @@ public class BDBHAMessageStoreManagerMBean extends AMQManagedObject implements M GROUP_MEMBER_ATTRIBUTE_TYPES ); GROUP_MEMBERS_TABLE = new TabularType("GroupMembers", "Replication group memebers", GROUP_MEMBER_ROW, - new String[] {BDBHAMessageStore.GRP_MEM_COL_NODE_NAME}); + new String[] {GRP_MEM_COL_NODE_NAME}); } catch (final OpenDataException ode) { @@ -79,104 +86,95 @@ public class BDBHAMessageStoreManagerMBean extends AMQManagedObject implements M } } - private final BDBHAMessageStore _store; + private final ReplicationNode _localReplicationNode; + private final String _objectName; + private final VirtualHost _parent; + private final String _virtualHostName; - protected BDBHAMessageStoreManagerMBean(BDBHAMessageStore store, ManagedObject parent) throws JMException + public BDBHAMessageStoreManagerMBean(ReplicationNode localReplicationNode, ManagedObject parent) throws JMException { super(ManagedBDBHAMessageStore.class, ManagedBDBHAMessageStore.TYPE, ((AMQManagedObject)parent).getRegistry()); - LOGGER.debug("Creating BDBHAMessageStoreManagerMBean"); - _store = store; + + _localReplicationNode = localReplicationNode; + _virtualHostName = localReplicationNode.getParent(VirtualHost.class).getName(); + _objectName = ObjectName.quote(_virtualHostName); + _parent = _localReplicationNode.getParent(VirtualHost.class); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Creating BDBHAMessageStoreManagerMBean for " + _localReplicationNode.getName()); + } register(); } @Override public String getObjectInstanceName() { - return ObjectName.quote(_store.getName()); + return _objectName; } @Override public String getGroupName() { - return _store.getGroupName(); + return (String) _localReplicationNode.getAttribute(GROUP_NAME); } @Override public String getNodeName() { - return _store.getNodeName(); + return (String) _localReplicationNode.getName(); } @Override public String getNodeHostPort() { - return _store.getNodeHostPort(); + return (String) _localReplicationNode.getAttribute(HOST_PORT); } @Override public String getHelperHostPort() { - return _store.getHelperHostPort(); + return (String) _localReplicationNode.getAttribute(HELPER_HOST_PORT); } @Override public String getDurability() throws IOException, JMException { - try - { - return _store.getDurability(); - } - catch (RuntimeException e) - { - LOGGER.debug("Failed query replication policy", e); - throw new JMException(e.getMessage()); - } + return (String) _localReplicationNode.getAttribute(DURABILITY); } @Override public boolean getCoalescingSync() throws IOException, JMException { - return _store.isCoalescingSync(); + return (Boolean)_localReplicationNode.getAttribute(COALESCING_SYNC); } @Override public String getNodeState() throws IOException, JMException { - try - { - return _store.getNodeState(); - } - catch (RuntimeException e) - { - LOGGER.debug("Failed query node state", e); - throw new JMException(e.getMessage()); - } + return (String)_localReplicationNode.getAttribute(ROLE); } @Override public boolean getDesignatedPrimary() throws IOException, JMException { - try - { - return _store.isDesignatedPrimary(); - } - catch (RuntimeException e) - { - LOGGER.debug("Failed query designated primary", e); - throw new JMException(e.getMessage()); - } + return (Boolean)_localReplicationNode.getAttribute(DESIGNATED_PRIMARY); } @Override public TabularData getAllNodesInGroup() throws IOException, JMException { - final TabularDataSupport data = new TabularDataSupport(GROUP_MEMBERS_TABLE); - final List<Map<String, String>> members = _store.getGroupMembers(); + Collection<ReplicationNode> allNodes = _parent.getChildren(ReplicationNode.class); - for (Map<String, String> map : members) + final TabularDataSupport data = new TabularDataSupport(GROUP_MEMBERS_TABLE); + for (ReplicationNode replicationNode : allNodes) { - CompositeData memberData = new CompositeDataSupport(GROUP_MEMBER_ROW, map); + Map<String, String> nodeMap = new HashMap<String, String>(); + nodeMap.put(GRP_MEM_COL_NODE_NAME, replicationNode.getName()); + nodeMap.put(GRP_MEM_COL_NODE_HOST_PORT, (String)replicationNode.getAttribute(HOST_PORT)); + + CompositeData memberData = new CompositeDataSupport(GROUP_MEMBER_ROW, nodeMap); data.put(memberData); } return data; @@ -185,14 +183,26 @@ public class BDBHAMessageStoreManagerMBean extends AMQManagedObject implements M @Override public void removeNodeFromGroup(String nodeName) throws JMException { + // find the replication node object, set the desired state + Collection<ReplicationNode> allNodes = _parent.getChildren(ReplicationNode.class); + ReplicationNode targetNode = ConfiguredObjectFinder.findConfiguredObjectByName(allNodes, nodeName); + + if (targetNode == null) + { + throw new JMException("Failed to find replication node with name '" + nodeName + "'."); + } try { - _store.removeNodeFromGroup(nodeName); + State newState = targetNode.setDesiredState(targetNode.getActualState(), State.DELETED); + if (newState != State.DELETED) + { + throw new JMException("Failed to delete replication node with name '" + nodeName + "'. New unexpectedly state is " + newState); + } } - catch (AMQStoreException e) + catch(IllegalStateTransitionException e) { - LOGGER.error("Failed to remove node " + nodeName + " from group", e); - throw new JMException(e.getMessage()); + LOGGER.error("Cannot remove node '" + nodeName + "' from the group", e); + throw new JMException("Cannot remove node '" + nodeName + "' from the group:" + e.getMessage()); } } @@ -201,27 +211,19 @@ public class BDBHAMessageStoreManagerMBean extends AMQManagedObject implements M { try { - _store.setDesignatedPrimary(primary); + _localReplicationNode.setAttribute(DESIGNATED_PRIMARY, _localReplicationNode.getAttribute(DESIGNATED_PRIMARY), primary); } - catch (AMQStoreException e) + catch (Exception e) { - LOGGER.error("Failed to set node " + _store.getNodeName() + " as designated primary", e); - throw new JMException(e.getMessage()); + LOGGER.error("Failed to set node " + _localReplicationNode.getName() + " to designated primary : " + primary, e); + throw new JMException("Failed to set node " + _localReplicationNode.getName() + " to designated primary : " + primary + ":" + e.getMessage()); } } @Override public void updateAddress(String nodeName, String newHostName, int newPort) throws JMException { - try - { - _store.updateAddress(nodeName, newHostName, newPort); - } - catch(AMQStoreException e) - { - LOGGER.error("Failed to update address for node " + nodeName + " to " + newHostName + ":" + newPort, e); - throw new JMException(e.getMessage()); - } + throw new UnsupportedOperationException("Unsupported operation. Delete the node then add a new node in its place."); } @Override diff --git a/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBeanProvider.java b/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBeanProvider.java index 0492350a25..1c362c3023 100644 --- a/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBeanProvider.java +++ b/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBeanProvider.java @@ -21,18 +21,17 @@ package org.apache.qpid.server.store.berkeleydb.jmx; import javax.management.JMException; -import javax.management.StandardMBean; import org.apache.log4j.Logger; import org.apache.qpid.server.jmx.MBeanProvider; import org.apache.qpid.server.jmx.ManagedObject; import org.apache.qpid.server.model.ConfiguredObject; -import org.apache.qpid.server.model.VirtualHost; -import org.apache.qpid.server.store.berkeleydb.BDBHAMessageStore; +import org.apache.qpid.server.store.berkeleydb.replication.LocalReplicationNode; +import org.apache.qpid.server.store.berkeleydb.replication.ReplicatedEnvironmentFacade; /** * This provide will create a {@link BDBHAMessageStoreManagerMBean} if the child is a virtual - * host and of type {@link BDBHAMessageStore#TYPE}. + * host and of type {@link ReplicatedEnvironmentFacade#TYPE}. * */ public class BDBHAMessageStoreManagerMBeanProvider implements MBeanProvider @@ -47,23 +46,20 @@ public class BDBHAMessageStoreManagerMBeanProvider implements MBeanProvider @Override public boolean isChildManageableByMBean(ConfiguredObject child) { - return (child instanceof VirtualHost - && BDBHAMessageStore.TYPE.equals(child.getAttribute(VirtualHost.STORE_TYPE))); + return (child instanceof LocalReplicationNode); } @Override - public StandardMBean createMBean(ConfiguredObject child, StandardMBean parent) throws JMException + public ManagedObject createMBean(ConfiguredObject child, ManagedObject parent) throws JMException { - VirtualHost virtualHostChild = (VirtualHost) child; - - BDBHAMessageStore messageStore = (BDBHAMessageStore) virtualHostChild.getMessageStore(); + LocalReplicationNode localReplicationNode = (LocalReplicationNode) child; if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creating mBean for child " + child); } - return new BDBHAMessageStoreManagerMBean(messageStore, (ManagedObject) parent); + return new BDBHAMessageStoreManagerMBean(localReplicationNode, parent); } @Override diff --git a/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/ManagedBDBHAMessageStore.java b/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/ManagedBDBHAMessageStore.java index b85e44526b..fc1cd0801a 100644 --- a/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/ManagedBDBHAMessageStore.java +++ b/qpid/java/bdbstore/jmx/src/main/java/org/apache/qpid/server/store/berkeleydb/jmx/ManagedBDBHAMessageStore.java @@ -41,6 +41,9 @@ public interface ManagedBDBHAMessageStore public static final String ATTR_DESIGNATED_PRIMARY = "DesignatedPrimary"; public static final String ATTR_COALESCING_SYNC = "CoalescingSync"; + public static final String GRP_MEM_COL_NODE_HOST_PORT = "NodeHostPort"; + public static final String GRP_MEM_COL_NODE_NAME = "NodeName"; + @MBeanAttribute(name=ATTR_GROUP_NAME, description="Name identifying the group") String getGroupName() throws IOException, JMException; diff --git a/qpid/java/bdbstore/jmx/src/test/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBeanTest.java b/qpid/java/bdbstore/jmx/src/test/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBeanTest.java index 298d5bc045..b5e01732dd 100644 --- a/qpid/java/bdbstore/jmx/src/test/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBeanTest.java +++ b/qpid/java/bdbstore/jmx/src/test/java/org/apache/qpid/server/store/berkeleydb/jmx/BDBHAMessageStoreManagerMBeanTest.java @@ -19,15 +19,21 @@ */ package org.apache.qpid.server.store.berkeleydb.jmx; -import static org.mockito.Mockito.doThrow; +import static org.apache.qpid.server.model.ReplicationNode.COALESCING_SYNC; +import static org.apache.qpid.server.model.ReplicationNode.DESIGNATED_PRIMARY; +import static org.apache.qpid.server.model.ReplicationNode.DURABILITY; +import static org.apache.qpid.server.model.ReplicationNode.GROUP_NAME; +import static org.apache.qpid.server.model.ReplicationNode.HELPER_HOST_PORT; +import static org.apache.qpid.server.model.ReplicationNode.HOST_PORT; +import static org.apache.qpid.server.model.ReplicationNode.ROLE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.jar.JarException; import javax.management.JMException; import javax.management.ObjectName; @@ -37,15 +43,15 @@ import javax.management.openmbean.TabularData; import junit.framework.TestCase; -import org.apache.qpid.AMQStoreException; import org.apache.qpid.server.jmx.AMQManagedObject; import org.apache.qpid.server.jmx.ManagedObjectRegistry; import org.apache.qpid.server.logging.SystemOutMessageLogger; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.actors.TestLogActor; -import org.apache.qpid.server.store.berkeleydb.BDBHAMessageStore; -import org.apache.qpid.server.store.berkeleydb.jmx.BDBHAMessageStoreManagerMBean; -import org.apache.qpid.server.store.berkeleydb.jmx.ManagedBDBHAMessageStore; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; public class BDBHAMessageStoreManagerMBeanTest extends TestCase { @@ -55,11 +61,12 @@ public class BDBHAMessageStoreManagerMBeanTest extends TestCase private static final String TEST_HELPER_HOST_PORT = "host:5678"; private static final String TEST_DURABILITY = "sync,sync,all"; private static final String TEST_NODE_STATE = "MASTER"; - private static final String TEST_STORE_NAME = "testStoreName"; private static final boolean TEST_DESIGNATED_PRIMARY_FLAG = false; + private static final String TEST_VHOST_NAME = "test"; - private BDBHAMessageStore _store; private BDBHAMessageStoreManagerMBean _mBean; + private VirtualHost _virtualHost; + private ReplicationNode _localReplicationNode; private AMQManagedObject _mBeanParent; @Override @@ -68,10 +75,16 @@ public class BDBHAMessageStoreManagerMBeanTest extends TestCase super.setUp(); CurrentActor.set(new TestLogActor(new SystemOutMessageLogger())); - _store = mock(BDBHAMessageStore.class); + _localReplicationNode = mock(ReplicationNode.class); + _virtualHost = mock(VirtualHost.class); _mBeanParent = mock(AMQManagedObject.class); when(_mBeanParent.getRegistry()).thenReturn(mock(ManagedObjectRegistry.class)); - _mBean = new BDBHAMessageStoreManagerMBean(_store, _mBeanParent); + when(_localReplicationNode.getParent(VirtualHost.class)).thenReturn(_virtualHost); + + when(_localReplicationNode.getName()).thenReturn(TEST_NODE_NAME); + when(_virtualHost.getName()).thenReturn(TEST_VHOST_NAME); + + _mBean = new BDBHAMessageStoreManagerMBean(_localReplicationNode, _mBeanParent); } @Override @@ -83,103 +96,124 @@ public class BDBHAMessageStoreManagerMBeanTest extends TestCase public void testObjectName() throws Exception { - when(_store.getName()).thenReturn(TEST_STORE_NAME); - - String expectedObjectName = "org.apache.qpid:type=BDBHAMessageStore,name=" + ObjectName.quote(TEST_STORE_NAME); + String expectedObjectName = "org.apache.qpid:type=BDBHAMessageStore,name=" + ObjectName.quote(TEST_VHOST_NAME); assertEquals(expectedObjectName, _mBean.getObjectName().toString()); } public void testGroupName() throws Exception { - when(_store.getGroupName()).thenReturn(TEST_GROUP_NAME); + when(_localReplicationNode.getAttribute(GROUP_NAME)).thenReturn(TEST_GROUP_NAME); assertEquals(TEST_GROUP_NAME, _mBean.getAttribute(ManagedBDBHAMessageStore.ATTR_GROUP_NAME)); } public void testNodeName() throws Exception { - when(_store.getNodeName()).thenReturn(TEST_NODE_NAME); - assertEquals(TEST_NODE_NAME, _mBean.getAttribute(ManagedBDBHAMessageStore.ATTR_NODE_NAME)); } public void testNodeHostPort() throws Exception { - when(_store.getNodeHostPort()).thenReturn(TEST_NODE_HOST_PORT); + when(_localReplicationNode.getAttribute(HOST_PORT)).thenReturn(TEST_NODE_HOST_PORT); assertEquals(TEST_NODE_HOST_PORT, _mBean.getAttribute(ManagedBDBHAMessageStore.ATTR_NODE_HOST_PORT)); } public void testHelperHostPort() throws Exception { - when(_store.getHelperHostPort()).thenReturn(TEST_HELPER_HOST_PORT); + when(_localReplicationNode.getAttribute(HELPER_HOST_PORT)).thenReturn(TEST_HELPER_HOST_PORT); assertEquals(TEST_HELPER_HOST_PORT, _mBean.getAttribute(ManagedBDBHAMessageStore.ATTR_HELPER_HOST_PORT)); } public void testDurability() throws Exception { - when(_store.getDurability()).thenReturn(TEST_DURABILITY); + when(_localReplicationNode.getAttribute(DURABILITY)).thenReturn(TEST_DURABILITY); assertEquals(TEST_DURABILITY, _mBean.getAttribute(ManagedBDBHAMessageStore.ATTR_DURABILITY)); } public void testCoalescingSync() throws Exception { - when(_store.isCoalescingSync()).thenReturn(true); + when(_localReplicationNode.getAttribute(COALESCING_SYNC)).thenReturn(true); assertEquals(true, _mBean.getAttribute(ManagedBDBHAMessageStore.ATTR_COALESCING_SYNC)); } public void testNodeState() throws Exception { - when(_store.getNodeState()).thenReturn(TEST_NODE_STATE); + when(_localReplicationNode.getAttribute(ROLE)).thenReturn(TEST_NODE_STATE); assertEquals(TEST_NODE_STATE, _mBean.getAttribute(ManagedBDBHAMessageStore.ATTR_NODE_STATE)); } public void testDesignatedPrimaryFlag() throws Exception { - when(_store.isDesignatedPrimary()).thenReturn(TEST_DESIGNATED_PRIMARY_FLAG); + when(_localReplicationNode.getAttribute(DESIGNATED_PRIMARY)).thenReturn(TEST_DESIGNATED_PRIMARY_FLAG); assertEquals(TEST_DESIGNATED_PRIMARY_FLAG, _mBean.getAttribute(ManagedBDBHAMessageStore.ATTR_DESIGNATED_PRIMARY)); } public void testGroupMembersForGroupWithOneNode() throws Exception { - List<Map<String, String>> members = Collections.singletonList(createTestNodeResult()); - when(_store.getGroupMembers()).thenReturn(members); + ReplicationNode remoteNode = mock(ReplicationNode.class); + when(remoteNode.getName()).thenReturn("remotenode"); + when(remoteNode.getAttribute(HOST_PORT)).thenReturn("remotehost:port"); + + when(_localReplicationNode.getAttribute(HOST_PORT)).thenReturn(TEST_NODE_HOST_PORT); + + Collection<ReplicationNode> nodes = new ArrayList<ReplicationNode>(); + nodes.add(_localReplicationNode); + nodes.add(remoteNode); + when(_virtualHost.getChildren(ReplicationNode.class)).thenReturn(nodes); final TabularData resultsTable = _mBean.getAllNodesInGroup(); - assertTableHasHeadingsNamed(resultsTable, BDBHAMessageStore.GRP_MEM_COL_NODE_NAME, BDBHAMessageStore.GRP_MEM_COL_NODE_HOST_PORT); + assertTableHasHeadingsNamed(resultsTable, BDBHAMessageStoreManagerMBean.GRP_MEM_COL_NODE_NAME, BDBHAMessageStoreManagerMBean.GRP_MEM_COL_NODE_HOST_PORT); final int numberOfDataRows = resultsTable.size(); - assertEquals("Unexpected number of data rows", 1 ,numberOfDataRows); - final CompositeData row = (CompositeData) resultsTable.values().iterator().next(); - assertEquals(TEST_NODE_NAME, row.get(BDBHAMessageStore.GRP_MEM_COL_NODE_NAME)); - assertEquals(TEST_NODE_HOST_PORT, row.get(BDBHAMessageStore.GRP_MEM_COL_NODE_HOST_PORT)); + assertEquals("Unexpected number of data rows", 2 ,numberOfDataRows); + Iterator<?> iterator = resultsTable.values().iterator(); + + final CompositeData firstRow = (CompositeData) iterator.next(); + assertEquals(TEST_NODE_NAME, firstRow.get(BDBHAMessageStoreManagerMBean.GRP_MEM_COL_NODE_NAME)); + assertEquals(TEST_NODE_HOST_PORT, firstRow.get(BDBHAMessageStoreManagerMBean.GRP_MEM_COL_NODE_HOST_PORT)); + + final CompositeData secondRow = (CompositeData) iterator.next(); + assertEquals("remotenode", secondRow.get(BDBHAMessageStoreManagerMBean.GRP_MEM_COL_NODE_NAME)); + assertEquals("remotehost:port", secondRow.get(BDBHAMessageStoreManagerMBean.GRP_MEM_COL_NODE_HOST_PORT)); + } public void testRemoveNodeFromReplicationGroup() throws Exception { + Collection<ReplicationNode> nodes = new ArrayList<ReplicationNode>(); + nodes.add(_localReplicationNode); + when(_virtualHost.getChildren(ReplicationNode.class)).thenReturn(nodes); + when(_localReplicationNode.getActualState()).thenReturn(State.ACTIVE); + when(_localReplicationNode.setDesiredState(State.ACTIVE, State.DELETED)).thenReturn(State.DELETED); + _mBean.removeNodeFromGroup(TEST_NODE_NAME); - verify(_store).removeNodeFromGroup(TEST_NODE_NAME); + verify(_localReplicationNode).setDesiredState(State.ACTIVE, State.DELETED); } - public void testRemoveNodeFromReplicationGroupWithError() throws Exception + public void testRemoveNodeFromReplicationGroupOnIllegalStateTransitionException() throws Exception { - doThrow(new AMQStoreException("mocked exception")).when(_store).removeNodeFromGroup(TEST_NODE_NAME); + Collection<ReplicationNode> nodes = new ArrayList<ReplicationNode>(); + nodes.add(_localReplicationNode); + when(_virtualHost.getChildren(ReplicationNode.class)).thenReturn(nodes); + when(_localReplicationNode.getActualState()).thenReturn(State.ACTIVE); + when(_localReplicationNode.setDesiredState(State.ACTIVE, State.DELETED)).thenThrow(new IllegalStateTransitionException()); try { _mBean.removeNodeFromGroup(TEST_NODE_NAME); - fail("Exception not thrown"); + fail("Should throw JM Exception on IllegalStateTransitionException"); } - catch (JMException je) + catch(JMException e) { - // PASS + //pass } } @@ -187,32 +221,7 @@ public class BDBHAMessageStoreManagerMBeanTest extends TestCase { _mBean.setDesignatedPrimary(true); - verify(_store).setDesignatedPrimary(true); - } - - public void testSetAsDesignatedPrimaryWithError() throws Exception - { - doThrow(new AMQStoreException("mocked exception")).when(_store).setDesignatedPrimary(true); - - try - { - _mBean.setDesignatedPrimary(true); - fail("Exception not thrown"); - } - catch (JMException je) - { - // PASS - } - } - - public void testUpdateAddress() throws Exception - { - String newHostName = "newHostName"; - int newPort = 1967; - - _mBean.updateAddress(TEST_NODE_NAME, newHostName, newPort); - - verify(_store).updateAddress(TEST_NODE_NAME, newHostName, newPort); + verify(_localReplicationNode).setAttribute(DESIGNATED_PRIMARY, null, true); } private void assertTableHasHeadingsNamed(final TabularData resultsTable, String... headingNames) @@ -223,12 +232,4 @@ public class BDBHAMessageStoreManagerMBeanTest extends TestCase assertTrue("Table should have column with heading " + headingName, headingsRow.containsKey(headingName)); } } - - private Map<String, String> createTestNodeResult() - { - Map<String, String> items = new HashMap<String, String>(); - items.put(BDBHAMessageStore.GRP_MEM_COL_NODE_NAME, TEST_NODE_NAME); - items.put(BDBHAMessageStore.GRP_MEM_COL_NODE_HOST_PORT, TEST_NODE_HOST_PORT); - return items; - } } diff --git a/qpid/java/bdbstore/jmx/src/test/java/org/apache/qpid/server/store/berkeleydb/jmx/VirtualHostMBeanTest.java b/qpid/java/bdbstore/jmx/src/test/java/org/apache/qpid/server/store/berkeleydb/jmx/VirtualHostMBeanTest.java new file mode 100644 index 0000000000..9995e9a02a --- /dev/null +++ b/qpid/java/bdbstore/jmx/src/test/java/org/apache/qpid/server/store/berkeleydb/jmx/VirtualHostMBeanTest.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.jmx; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import junit.framework.TestCase; + +import org.apache.qpid.server.jmx.DefaultManagedObject; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.jmx.mbeans.VirtualHostMBean; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.store.berkeleydb.replication.LocalReplicationNode; + +public class VirtualHostMBeanTest extends TestCase +{ + private VirtualHost _mockVirtualHost; + private ManagedObjectRegistry _mockManagedObjectRegistry; + private VirtualHostMBean _virtualHostMBean; + private LocalReplicationNode _mockLocalReplicationNode; + + @Override + protected void setUp() throws Exception + { + _mockVirtualHost = mock(VirtualHost.class); + _mockManagedObjectRegistry = mock(ManagedObjectRegistry.class); + _mockLocalReplicationNode = mock(LocalReplicationNode.class); + when(_mockLocalReplicationNode.getParent(VirtualHost.class)).thenReturn(_mockVirtualHost); + when(_mockVirtualHost.getName()).thenReturn("vhost"); + + _virtualHostMBean = new VirtualHostMBean(_mockVirtualHost, _mockManagedObjectRegistry); + } + + public void testAdditionalMbeanRegistered_LocalReplicationNodeAdded() throws Exception + { + _virtualHostMBean.childAdded(_mockVirtualHost, _mockLocalReplicationNode); + + verify(_mockManagedObjectRegistry, times(2)).registerObject(any(DefaultManagedObject.class)); + } + + public void testAdditionalMbeanUnregisteredOnUnregisterOfThisMbean() throws Exception + { + _virtualHostMBean.childAdded(_mockVirtualHost, _mockLocalReplicationNode); + _virtualHostMBean.unregister(); + + verify(_mockManagedObjectRegistry, times(2)).unregisterObject(any(DefaultManagedObject.class)); + } + + public void testAdditionalMbeanUnregistered_LocalReplicationNodeRemoved() throws Exception + { + _virtualHostMBean.childAdded(_mockVirtualHost, _mockLocalReplicationNode); + + _virtualHostMBean.childRemoved(_mockVirtualHost, _mockLocalReplicationNode); + verify(_mockManagedObjectRegistry).unregisterObject(any(BDBHAMessageStoreManagerMBean.class)); + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AbstractBDBMessageStore.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AbstractBDBMessageStore.java deleted file mode 100644 index 35efa55569..0000000000 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AbstractBDBMessageStore.java +++ /dev/null @@ -1,1896 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.qpid.server.store.berkeleydb; - -import com.sleepycat.bind.tuple.ByteBinding; -import com.sleepycat.bind.tuple.IntegerBinding; -import com.sleepycat.bind.tuple.LongBinding; -import com.sleepycat.je.*; -import com.sleepycat.je.Transaction; - -import java.io.File; -import java.lang.ref.SoftReference; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - -import org.apache.commons.configuration.Configuration; -import org.apache.commons.configuration.ConfigurationException; -import org.apache.log4j.Logger; -import org.apache.qpid.AMQStoreException; -import org.apache.qpid.server.message.EnqueueableMessage; -import org.apache.qpid.server.model.VirtualHost; -import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.store.*; -import org.apache.qpid.server.store.MessageStoreRecoveryHandler.StoredMessageRecoveryHandler; -import org.apache.qpid.server.store.TransactionLogRecoveryHandler.QueueEntryRecoveryHandler; -import org.apache.qpid.server.store.berkeleydb.entry.PreparedTransaction; -import org.apache.qpid.server.store.berkeleydb.entry.QueueEntryKey; -import org.apache.qpid.server.store.berkeleydb.entry.Xid; -import org.apache.qpid.server.store.berkeleydb.tuple.ConfiguredObjectBinding; -import org.apache.qpid.server.store.berkeleydb.tuple.ContentBinding; -import org.apache.qpid.server.store.berkeleydb.tuple.MessageMetaDataBinding; -import org.apache.qpid.server.store.berkeleydb.tuple.PreparedTransactionBinding; -import org.apache.qpid.server.store.berkeleydb.tuple.QueueEntryBinding; -import org.apache.qpid.server.store.berkeleydb.tuple.UUIDTupleBinding; -import org.apache.qpid.server.store.berkeleydb.tuple.XidBinding; -import org.apache.qpid.server.store.berkeleydb.upgrade.Upgrader; -import org.apache.qpid.util.FileUtils; - -public abstract class AbstractBDBMessageStore implements MessageStore, DurableConfigurationStore -{ - private static final Logger LOGGER = Logger.getLogger(AbstractBDBMessageStore.class); - - private static final int LOCK_RETRY_ATTEMPTS = 5; - - public static final int VERSION = 7; - - private static final Map<String, String> ENVCONFIG_DEFAULTS = Collections.unmodifiableMap(new HashMap<String, String>() - {{ - put(EnvironmentConfig.LOCK_N_LOCK_TABLES, "7"); - put(EnvironmentConfig.STATS_COLLECT, "false"); // Turn off stats generation - feature introduced (and on by default) from BDB JE 5.0.84 - }}); - - private final AtomicBoolean _closed = new AtomicBoolean(false); - - private Environment _environment; - - private static String CONFIGURED_OBJECTS = "CONFIGURED_OBJECTS"; - private static String MESSAGEMETADATADB_NAME = "MESSAGE_METADATA"; - private static String MESSAGECONTENTDB_NAME = "MESSAGE_CONTENT"; - private static String DELIVERYDB_NAME = "QUEUE_ENTRIES"; - private static String BRIDGEDB_NAME = "BRIDGES"; - private static String LINKDB_NAME = "LINKS"; - private static String XIDDB_NAME = "XIDS"; - private static String CONFIG_VERSION_DB = "CONFIG_VERSION"; - - private Database _configuredObjectsDb; - private Database _configVersionDb; - private Database _messageMetaDataDb; - private Database _messageContentDb; - private Database _deliveryDb; - private Database _bridgeDb; - private Database _linkDb; - private Database _xidDb; - - /* ======= - * Schema: - * ======= - * - * Queue: - * name(AMQShortString) - name(AMQShortString), owner(AMQShortString), - * arguments(FieldTable encoded as binary), exclusive (boolean) - * - * Exchange: - * name(AMQShortString) - name(AMQShortString), typeName(AMQShortString), autodelete (boolean) - * - * Binding: - * exchangeName(AMQShortString), queueName(AMQShortString), routingKey(AMQShortString), - * arguments (FieldTable encoded as binary) - 0 (zero) - * - * QueueEntry: - * queueName(AMQShortString), messageId (long) - 0 (zero) - * - * Message (MetaData): - * messageId (long) - bodySize (integer), metaData (MessageMetaData encoded as binary) - * - * Message (Content): - * messageId (long), byteOffset (integer) - dataLength(integer), data(binary) - */ - - private final AtomicLong _messageId = new AtomicLong(0); - - protected final StateManager _stateManager; - - private MessageStoreRecoveryHandler _messageRecoveryHandler; - - private TransactionLogRecoveryHandler _tlogRecoveryHandler; - - private ConfigurationRecoveryHandler _configRecoveryHandler; - - private long _totalStoreSize; - private boolean _limitBusted; - private long _persistentSizeLowThreshold; - private long _persistentSizeHighThreshold; - - private final EventManager _eventManager = new EventManager(); - private String _storeLocation; - - private Map<String, String> _envConfigMap; - private VirtualHost _virtualHost; - - public AbstractBDBMessageStore() - { - _stateManager = new StateManager(_eventManager); - } - - @Override - public void addEventListener(EventListener eventListener, Event... events) - { - _eventManager.addEventListener(eventListener, events); - } - - public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws Exception - { - _stateManager.attainState(State.INITIALISING); - - _configRecoveryHandler = recoveryHandler; - _virtualHost = virtualHost; - } - - public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler messageRecoveryHandler, - TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception - { - if(_stateManager.isInState(State.INITIAL)) - { - // Is acting as a message store, but not a durable config store - _stateManager.attainState(State.INITIALISING); - } - - _messageRecoveryHandler = messageRecoveryHandler; - _tlogRecoveryHandler = tlogRecoveryHandler; - _virtualHost = virtualHost; - - completeInitialisation(); - } - - private void completeInitialisation() throws Exception - { - configure(_virtualHost); - - _stateManager.attainState(State.INITIALISED); - } - - public synchronized void activate() throws Exception - { - // check if acting as a durable config store, but not a message store - if(_stateManager.isInState(State.INITIALISING)) - { - completeInitialisation(); - } - _stateManager.attainState(State.ACTIVATING); - - if(_configRecoveryHandler != null) - { - recoverConfig(_configRecoveryHandler); - } - if(_messageRecoveryHandler != null) - { - recoverMessages(_messageRecoveryHandler); - } - if(_tlogRecoveryHandler != null) - { - recoverQueueEntries(_tlogRecoveryHandler); - } - - _stateManager.attainState(State.ACTIVE); - } - - public org.apache.qpid.server.store.Transaction newTransaction() - { - return new BDBTransaction(); - } - - /** - * Called after instantiation in order to configure the message store. - * - * - * - * @param virtualHost The virtual host using this store - * @return whether a new store environment was created or not (to indicate whether recovery is necessary) - * - * @throws Exception If any error occurs that means the store is unable to configure itself. - */ - public void configure(VirtualHost virtualHost) throws Exception - { - configure(virtualHost, _messageRecoveryHandler != null); - } - - public void configure(VirtualHost virtualHost, boolean isMessageStore) throws Exception - { - String name = virtualHost.getName(); - final String defaultPath = System.getProperty("QPID_WORK") + File.separator + "bdbstore" + File.separator + name; - - String storeLocation; - if(isMessageStore) - { - storeLocation = (String) virtualHost.getAttribute(VirtualHost.STORE_PATH); - if(storeLocation == null) - { - storeLocation = defaultPath; - } - } - else // we are acting only as the durable config store - { - storeLocation = (String) virtualHost.getAttribute(VirtualHost.CONFIG_STORE_PATH); - if(storeLocation == null) - { - storeLocation = defaultPath; - } - } - - Object overfullAttr = virtualHost.getAttribute(MessageStoreConstants.OVERFULL_SIZE_ATTRIBUTE); - Object underfullAttr = virtualHost.getAttribute(MessageStoreConstants.UNDERFULL_SIZE_ATTRIBUTE); - - _persistentSizeHighThreshold = overfullAttr == null ? -1l : - overfullAttr instanceof Number ? ((Number) overfullAttr).longValue() : Long.parseLong(overfullAttr.toString()); - _persistentSizeLowThreshold = underfullAttr == null ? _persistentSizeHighThreshold : - underfullAttr instanceof Number ? ((Number) underfullAttr).longValue() : Long.parseLong(underfullAttr.toString()); - - - if(_persistentSizeLowThreshold > _persistentSizeHighThreshold || _persistentSizeLowThreshold < 0l) - { - _persistentSizeLowThreshold = _persistentSizeHighThreshold; - } - - File environmentPath = new File(storeLocation); - if (!environmentPath.exists()) - { - if (!environmentPath.mkdirs()) - { - throw new IllegalArgumentException("Environment path " + environmentPath + " could not be read or created. " - + "Ensure the path is correct and that the permissions are correct."); - } - } - - _storeLocation = storeLocation; - - _envConfigMap = new HashMap<String, String>(); - _envConfigMap.putAll(ENVCONFIG_DEFAULTS); - - Object bdbEnvConfigAttr = virtualHost.getAttribute("bdbEnvironmentConfig"); - if(bdbEnvConfigAttr instanceof Map) - { - _envConfigMap.putAll((Map)bdbEnvConfigAttr); - } - - LOGGER.info("Configuring BDB message store"); - - setupStore(environmentPath, name); - } - - protected Map<String,String> getConfigMap(Map<String, String> defaultConfig, Configuration config, String prefix) throws ConfigurationException - { - final List<Object> argumentNames = config.getList(prefix + ".name"); - final List<Object> argumentValues = config.getList(prefix + ".value"); - final int initialSize = argumentNames.size() + defaultConfig.size(); - - final Map<String,String> attributes = new HashMap<String,String>(initialSize); - attributes.putAll(defaultConfig); - - for (int i = 0; i < argumentNames.size(); i++) - { - final String argName = argumentNames.get(i).toString(); - final String argValue = argumentValues.get(i).toString(); - - attributes.put(argName, argValue); - } - - return Collections.unmodifiableMap(attributes); - } - - @Override - public String getStoreLocation() - { - return _storeLocation; - } - - /** - * Move the store state from INITIAL to ACTIVE without actually recovering. - * - * This is required if you do not want to perform recovery of the store data - * - * @throws AMQStoreException if the store is not in the correct state - */ - void startWithNoRecover() throws AMQStoreException - { - _stateManager.attainState(State.INITIALISING); - _stateManager.attainState(State.INITIALISED); - _stateManager.attainState(State.ACTIVATING); - _stateManager.attainState(State.ACTIVE); - } - - protected void setupStore(File storePath, String name) throws DatabaseException, AMQStoreException - { - _environment = createEnvironment(storePath); - - new Upgrader(_environment, name).upgradeIfNecessary(); - - openDatabases(); - - _totalStoreSize = getSizeOnDisk(); - } - - protected abstract Environment createEnvironment(File environmentPath) throws DatabaseException; - - public Environment getEnvironment() - { - return _environment; - } - - private void openDatabases() throws DatabaseException - { - DatabaseConfig dbConfig = new DatabaseConfig(); - dbConfig.setTransactional(true); - dbConfig.setAllowCreate(true); - - //This is required if we are wanting read only access. - dbConfig.setReadOnly(false); - - _configuredObjectsDb = openDatabase(CONFIGURED_OBJECTS, dbConfig); - _configVersionDb = openDatabase(CONFIG_VERSION_DB, dbConfig); - _messageMetaDataDb = openDatabase(MESSAGEMETADATADB_NAME, dbConfig); - _messageContentDb = openDatabase(MESSAGECONTENTDB_NAME, dbConfig); - _deliveryDb = openDatabase(DELIVERYDB_NAME, dbConfig); - _linkDb = openDatabase(LINKDB_NAME, dbConfig); - _bridgeDb = openDatabase(BRIDGEDB_NAME, dbConfig); - _xidDb = openDatabase(XIDDB_NAME, dbConfig); - } - - private Database openDatabase(final String dbName, final DatabaseConfig dbConfig) - { - // if opening read-only and the database doesn't exist, then you can't create it - return dbConfig.getReadOnly() && !_environment.getDatabaseNames().contains(dbName) - ? null - : _environment.openDatabase(null, dbName, dbConfig); - } - - /** - * Called to close and cleanup any resources used by the message store. - * - * @throws Exception If the close fails. - */ - public void close() throws Exception - { - if (_closed.compareAndSet(false, true)) - { - _stateManager.attainState(State.CLOSING); - closeInternal(); - _stateManager.attainState(State.CLOSED); - } - } - - protected void closeInternal() throws Exception - { - if (_messageMetaDataDb != null) - { - LOGGER.info("Closing message metadata database"); - _messageMetaDataDb.close(); - } - - if (_messageContentDb != null) - { - LOGGER.info("Closing message content database"); - _messageContentDb.close(); - } - - if (_configuredObjectsDb != null) - { - LOGGER.info("Closing configurable objects database"); - _configuredObjectsDb.close(); - } - - if (_deliveryDb != null) - { - LOGGER.info("Close delivery database"); - _deliveryDb.close(); - } - - if (_bridgeDb != null) - { - LOGGER.info("Close bridge database"); - _bridgeDb.close(); - } - - if (_linkDb != null) - { - LOGGER.info("Close link database"); - _linkDb.close(); - } - - - if (_xidDb != null) - { - LOGGER.info("Close xid database"); - _xidDb.close(); - } - - - if (_configVersionDb != null) - { - LOGGER.info("Close config version database"); - _configVersionDb.close(); - } - - closeEnvironment(); - - } - - private void closeEnvironment() throws DatabaseException - { - if (_environment != null) - { - // Clean the log before closing. This makes sure it doesn't contain - // redundant data. Closing without doing this means the cleaner may not - // get a chance to finish. - try - { - _environment.cleanLog(); - } - finally - { - _environment.close(); - } - } - } - - - private void recoverConfig(ConfigurationRecoveryHandler recoveryHandler) throws AMQStoreException - { - try - { - final int configVersion = getConfigVersion(); - recoveryHandler.beginConfigurationRecovery(this, configVersion); - loadConfiguredObjects(recoveryHandler); - - final int newConfigVersion = recoveryHandler.completeConfigurationRecovery(); - if(newConfigVersion != configVersion) - { - updateConfigVersion(newConfigVersion); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error recovering persistent state: " + e.getMessage(), e); - } - - } - - private void updateConfigVersion(int newConfigVersion) throws AMQStoreException - { - Cursor cursor = null; - try - { - Transaction txn = _environment.beginTransaction(null, null); - cursor = _configVersionDb.openCursor(txn, null); - DatabaseEntry key = new DatabaseEntry(); - ByteBinding.byteToEntry((byte) 0,key); - DatabaseEntry value = new DatabaseEntry(); - - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - IntegerBinding.intToEntry(newConfigVersion, value); - OperationStatus status = cursor.put(key, value); - if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Error setting config version: " + status); - } - } - cursor.close(); - cursor = null; - txn.commit(); - } - finally - { - closeCursorSafely(cursor); - } - - } - - private int getConfigVersion() throws AMQStoreException - { - Cursor cursor = null; - try - { - cursor = _configVersionDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - return IntegerBinding.entryToInt(value); - } - - // Insert 0 as the default config version - IntegerBinding.intToEntry(0,value); - ByteBinding.byteToEntry((byte) 0,key); - OperationStatus status = _configVersionDb.put(null, key, value); - if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Error initialising config version: " + status); - } - return 0; - } - finally - { - closeCursorSafely(cursor); - } - } - - private void loadConfiguredObjects(ConfigurationRecoveryHandler crh) throws DatabaseException - { - Cursor cursor = null; - try - { - cursor = _configuredObjectsDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - UUID id = UUIDTupleBinding.getInstance().entryToObject(key); - - ConfiguredObjectRecord configuredObject = new ConfiguredObjectBinding(id).entryToObject(value); - crh.configuredObject(configuredObject.getId(),configuredObject.getType(),configuredObject.getAttributes()); - } - - } - finally - { - closeCursorSafely(cursor); - } - } - - private void closeCursorSafely(Cursor cursor) - { - if (cursor != null) - { - cursor.close(); - } - } - - - private void recoverMessages(MessageStoreRecoveryHandler msrh) throws DatabaseException - { - StoredMessageRecoveryHandler mrh = msrh.begin(); - - Cursor cursor = null; - try - { - cursor = _messageMetaDataDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - MessageMetaDataBinding valueBinding = MessageMetaDataBinding.getInstance(); - - long maxId = 0; - - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - long messageId = LongBinding.entryToLong(key); - StorableMessageMetaData metaData = valueBinding.entryToObject(value); - - StoredBDBMessage message = new StoredBDBMessage(messageId, metaData, true); - - mrh.message(message); - - maxId = Math.max(maxId, messageId); - } - - _messageId.set(maxId); - } - catch (DatabaseException e) - { - LOGGER.error("Database Error: " + e.getMessage(), e); - throw e; - } - finally - { - closeCursorSafely(cursor); - } - } - - private void recoverQueueEntries(TransactionLogRecoveryHandler recoveryHandler) - throws DatabaseException - { - QueueEntryRecoveryHandler qerh = recoveryHandler.begin(this); - - ArrayList<QueueEntryKey> entries = new ArrayList<QueueEntryKey>(); - - Cursor cursor = null; - try - { - cursor = _deliveryDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); - - DatabaseEntry value = new DatabaseEntry(); - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - QueueEntryKey qek = keyBinding.entryToObject(key); - - entries.add(qek); - } - - try - { - cursor.close(); - } - finally - { - cursor = null; - } - - for(QueueEntryKey entry : entries) - { - UUID queueId = entry.getQueueId(); - long messageId = entry.getMessageId(); - qerh.queueEntry(queueId, messageId); - } - } - catch (DatabaseException e) - { - LOGGER.error("Database Error: " + e.getMessage(), e); - throw e; - } - finally - { - closeCursorSafely(cursor); - } - - TransactionLogRecoveryHandler.DtxRecordRecoveryHandler dtxrh = qerh.completeQueueEntryRecovery(); - - cursor = null; - try - { - cursor = _xidDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - XidBinding keyBinding = XidBinding.getInstance(); - PreparedTransactionBinding valueBinding = new PreparedTransactionBinding(); - DatabaseEntry value = new DatabaseEntry(); - - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - Xid xid = keyBinding.entryToObject(key); - PreparedTransaction preparedTransaction = valueBinding.entryToObject(value); - dtxrh.dtxRecord(xid.getFormat(),xid.getGlobalId(),xid.getBranchId(), - preparedTransaction.getEnqueues(),preparedTransaction.getDequeues()); - } - - } - catch (DatabaseException e) - { - LOGGER.error("Database Error: " + e.getMessage(), e); - throw e; - } - finally - { - closeCursorSafely(cursor); - } - - - dtxrh.completeDtxRecordRecovery(); - } - - public void removeMessage(long messageId, boolean sync) throws AMQStoreException - { - - boolean complete = false; - com.sleepycat.je.Transaction tx = null; - - Random rand = null; - int attempts = 0; - try - { - do - { - tx = null; - try - { - tx = _environment.beginTransaction(null, null); - - //remove the message meta data from the store - DatabaseEntry key = new DatabaseEntry(); - LongBinding.longToEntry(messageId, key); - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Removing message id " + messageId); - } - - - OperationStatus status = _messageMetaDataDb.delete(tx, key); - if (status == OperationStatus.NOTFOUND) - { - LOGGER.info("Message not found (attempt to remove failed - probably application initiated rollback) " + - messageId); - } - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Deleted metadata for message " + messageId); - } - - //now remove the content data from the store if there is any. - DatabaseEntry contentKeyEntry = new DatabaseEntry(); - LongBinding.longToEntry(messageId, contentKeyEntry); - _messageContentDb.delete(tx, contentKeyEntry); - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Deleted content for message " + messageId); - } - - commit(tx, sync); - complete = true; - tx = null; - } - catch (LockConflictException e) - { - try - { - if(tx != null) - { - tx.abort(); - } - } - catch(DatabaseException e2) - { - LOGGER.warn("Unable to abort transaction after LockConflictExcption", e2); - // rethrow the original log conflict exception, the secondary exception should already have - // been logged. - throw e; - } - - - LOGGER.warn("Lock timeout exception. Retrying (attempt " - + (attempts+1) + " of "+ LOCK_RETRY_ATTEMPTS +") " + e); - - if(++attempts < LOCK_RETRY_ATTEMPTS) - { - if(rand == null) - { - rand = new Random(); - } - - try - { - Thread.sleep(500l + (long)(500l * rand.nextDouble())); - } - catch (InterruptedException e1) - { - - } - } - else - { - // rethrow the lock conflict exception since we could not solve by retrying - throw e; - } - } - } - while(!complete); - } - catch (DatabaseException e) - { - LOGGER.error("Unexpected BDB exception", e); - - if (tx != null) - { - try - { - tx.abort(); - tx = null; - } - catch (DatabaseException e1) - { - throw new AMQStoreException("Error aborting transaction " + e1, e1); - } - } - - throw new AMQStoreException("Error removing message with id " + messageId + " from database: " + e.getMessage(), e); - } - finally - { - if (tx != null) - { - try - { - tx.abort(); - tx = null; - } - catch (DatabaseException e1) - { - throw new AMQStoreException("Error aborting transaction " + e1, e1); - } - } - } - } - - @Override - public void create(UUID id, String type, Map<String, Object> attributes) throws AMQStoreException - { - if (_stateManager.isInState(State.ACTIVE)) - { - ConfiguredObjectRecord configuredObject = new ConfiguredObjectRecord(id, type, attributes); - storeConfiguredObjectEntry(configuredObject); - } - } - - @Override - public void remove(UUID id, String type) throws AMQStoreException - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("public void remove(id = " + id + ", type="+type+"): called"); - } - OperationStatus status = removeConfiguredObject(null, id); - if (status == OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Configured object of type " + type + " with id " + id + " not found"); - } - } - - @Override - public UUID[] removeConfiguredObjects(final UUID... objects) throws AMQStoreException - { - com.sleepycat.je.Transaction txn = _environment.beginTransaction(null, null); - Collection<UUID> removed = new ArrayList<UUID>(objects.length); - for(UUID id : objects) - { - if(removeConfiguredObject(txn, id) == OperationStatus.SUCCESS) - { - removed.add(id); - } - } - - txn.commit(); - return removed.toArray(new UUID[removed.size()]); - } - - @Override - public void update(UUID id, String type, Map<String, Object> attributes) throws AMQStoreException - { - update(false, id, type, attributes, null); - } - - public void update(ConfiguredObjectRecord... records) throws AMQStoreException - { - update(false, records); - } - - public void update(boolean createIfNecessary, ConfiguredObjectRecord... records) throws AMQStoreException - { - com.sleepycat.je.Transaction txn = _environment.beginTransaction(null, null); - for(ConfiguredObjectRecord record : records) - { - update(createIfNecessary, record.getId(), record.getType(), record.getAttributes(), txn); - } - txn.commit(); - } - - private void update(boolean createIfNecessary, UUID id, String type, Map<String, Object> attributes, com.sleepycat.je.Transaction txn) throws AMQStoreException - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Updating " +type + ", id: " + id); - } - - try - { - DatabaseEntry key = new DatabaseEntry(); - UUIDTupleBinding keyBinding = UUIDTupleBinding.getInstance(); - keyBinding.objectToEntry(id, key); - - DatabaseEntry value = new DatabaseEntry(); - DatabaseEntry newValue = new DatabaseEntry(); - ConfiguredObjectBinding configuredObjectBinding = ConfiguredObjectBinding.getInstance(); - - OperationStatus status = _configuredObjectsDb.get(txn, key, value, LockMode.DEFAULT); - if (status == OperationStatus.SUCCESS || (createIfNecessary && status == OperationStatus.NOTFOUND)) - { - ConfiguredObjectRecord newQueueRecord = new ConfiguredObjectRecord(id, type, attributes); - - // write the updated entry to the store - configuredObjectBinding.objectToEntry(newQueueRecord, newValue); - status = _configuredObjectsDb.put(txn, key, newValue); - if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Error updating queue details within the store: " + status); - } - } - else if (status != OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Error finding queue details within the store: " + status); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error updating queue details within the store: " + e,e); - } - } - - /** - * Places a message onto a specified queue, in a given transaction. - * - * @param tx The transaction for the operation. - * @param queue The the queue to place the message on. - * @param messageId The message to enqueue. - * - * @throws AMQStoreException If the operation fails for any reason. - */ - public void enqueueMessage(final com.sleepycat.je.Transaction tx, final TransactionLogResource queue, - long messageId) throws AMQStoreException - { - - DatabaseEntry key = new DatabaseEntry(); - QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); - QueueEntryKey dd = new QueueEntryKey(queue.getId(), messageId); - keyBinding.objectToEntry(dd, key); - DatabaseEntry value = new DatabaseEntry(); - ByteBinding.byteToEntry((byte) 0, value); - - try - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Enqueuing message " + messageId + " on queue " - + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + queue.getId() - + " in transaction " + tx); - } - _deliveryDb.put(tx, key, value); - } - catch (DatabaseException e) - { - LOGGER.error("Failed to enqueue: " + e.getMessage(), e); - throw new AMQStoreException("Error writing enqueued message with id " + messageId + " for queue " - + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + queue.getId() - + " to database", e); - } - } - - /** - * Extracts a message from a specified queue, in a given transaction. - * - * @param tx The transaction for the operation. - * @param queue The queue to take the message from. - * @param messageId The message to dequeue. - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - public void dequeueMessage(final com.sleepycat.je.Transaction tx, final TransactionLogResource queue, - long messageId) throws AMQStoreException - { - - DatabaseEntry key = new DatabaseEntry(); - QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); - QueueEntryKey queueEntryKey = new QueueEntryKey(queue.getId(), messageId); - UUID id = queue.getId(); - keyBinding.objectToEntry(queueEntryKey, key); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Dequeue message id " + messageId + " from queue " - + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id); - } - - try - { - - OperationStatus status = _deliveryDb.delete(tx, key); - if (status == OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Unable to find message with id " + messageId + " on queue " - + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id); - } - else if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Unable to remove message with id " + messageId + " on queue" - + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id); - } - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Removed message " + messageId + " on queue " - + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id - + " from delivery db"); - - } - } - catch (DatabaseException e) - { - - LOGGER.error("Failed to dequeue message " + messageId + ": " + e.getMessage(), e); - LOGGER.error(tx); - - throw new AMQStoreException("Error accessing database while dequeuing message: " + e.getMessage(), e); - } - } - - - private void recordXid(com.sleepycat.je.Transaction txn, - long format, - byte[] globalId, - byte[] branchId, - org.apache.qpid.server.store.Transaction.Record[] enqueues, - org.apache.qpid.server.store.Transaction.Record[] dequeues) throws AMQStoreException - { - DatabaseEntry key = new DatabaseEntry(); - Xid xid = new Xid(format, globalId, branchId); - XidBinding keyBinding = XidBinding.getInstance(); - keyBinding.objectToEntry(xid,key); - - DatabaseEntry value = new DatabaseEntry(); - PreparedTransaction preparedTransaction = new PreparedTransaction(enqueues, dequeues); - PreparedTransactionBinding valueBinding = new PreparedTransactionBinding(); - valueBinding.objectToEntry(preparedTransaction, value); - - try - { - _xidDb.put(txn, key, value); - } - catch (DatabaseException e) - { - LOGGER.error("Failed to write xid: " + e.getMessage(), e); - throw new AMQStoreException("Error writing xid to database", e); - } - } - - private void removeXid(com.sleepycat.je.Transaction txn, long format, byte[] globalId, byte[] branchId) - throws AMQStoreException - { - DatabaseEntry key = new DatabaseEntry(); - Xid xid = new Xid(format, globalId, branchId); - XidBinding keyBinding = XidBinding.getInstance(); - - keyBinding.objectToEntry(xid, key); - - - try - { - - OperationStatus status = _xidDb.delete(txn, key); - if (status == OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Unable to find xid"); - } - else if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Unable to remove xid"); - } - - } - catch (DatabaseException e) - { - - LOGGER.error("Failed to remove xid ", e); - LOGGER.error(txn); - - throw new AMQStoreException("Error accessing database while removing xid: " + e.getMessage(), e); - } - } - - /** - * Commits all operations performed within a given transaction. - * - * @param tx The transaction to commit all operations for. - * - * @throws AMQStoreException If the operation fails for any reason. - */ - private StoreFuture commitTranImpl(final com.sleepycat.je.Transaction tx, boolean syncCommit) throws AMQStoreException - { - if (tx == null) - { - throw new AMQStoreException("Fatal internal error: transactional is null at commitTran"); - } - - StoreFuture result; - try - { - result = commit(tx, syncCommit); - - if (LOGGER.isDebugEnabled()) - { - String transactionType = syncCommit ? "synchronous" : "asynchronous"; - LOGGER.debug("commitTranImpl completed " + transactionType + " transaction " + tx); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error commit tx: " + e.getMessage(), e); - } - - return result; - } - - /** - * Abandons all operations performed within a given transaction. - * - * @param tx The transaction to abandon. - * - * @throws AMQStoreException If the operation fails for any reason. - */ - public void abortTran(final com.sleepycat.je.Transaction tx) throws AMQStoreException - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("abortTran called for transaction " + tx); - } - - try - { - tx.abort(); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error aborting transaction: " + e.getMessage(), e); - } - } - - /** - * Primarily for testing purposes. - * - * @param queueId - * - * @return a list of message ids for messages enqueued for a particular queue - */ - List<Long> getEnqueuedMessages(UUID queueId) throws AMQStoreException - { - Cursor cursor = null; - try - { - cursor = _deliveryDb.openCursor(null, null); - - DatabaseEntry key = new DatabaseEntry(); - - QueueEntryKey dd = new QueueEntryKey(queueId, 0); - - QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); - keyBinding.objectToEntry(dd, key); - - DatabaseEntry value = new DatabaseEntry(); - - LinkedList<Long> messageIds = new LinkedList<Long>(); - - OperationStatus status = cursor.getSearchKeyRange(key, value, LockMode.DEFAULT); - dd = keyBinding.entryToObject(key); - - while ((status == OperationStatus.SUCCESS) && dd.getQueueId().equals(queueId)) - { - - messageIds.add(dd.getMessageId()); - status = cursor.getNext(key, value, LockMode.DEFAULT); - if (status == OperationStatus.SUCCESS) - { - dd = keyBinding.entryToObject(key); - } - } - - return messageIds; - } - catch (DatabaseException e) - { - throw new AMQStoreException("Database error: " + e.getMessage(), e); - } - finally - { - if (cursor != null) - { - try - { - cursor.close(); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error closing cursor: " + e.getMessage(), e); - } - } - } - } - - /** - * Return a valid, currently unused message id. - * - * @return A fresh message id. - */ - public long getNewMessageId() - { - return _messageId.incrementAndGet(); - } - - /** - * Stores a chunk of message data. - * - * @param tx The transaction for the operation. - * @param messageId The message to store the data for. - * @param offset The offset of the data chunk in the message. - * @param contentBody The content of the data chunk. - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - protected void addContent(final com.sleepycat.je.Transaction tx, long messageId, int offset, - ByteBuffer contentBody) throws AMQStoreException - { - DatabaseEntry key = new DatabaseEntry(); - LongBinding.longToEntry(messageId, key); - DatabaseEntry value = new DatabaseEntry(); - ContentBinding messageBinding = ContentBinding.getInstance(); - messageBinding.objectToEntry(contentBody.array(), value); - try - { - OperationStatus status = _messageContentDb.put(tx, key, value); - if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Error adding content for message id " + messageId + ": " + status); - } - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Storing content for message " + messageId + " in transaction " + tx); - - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing AMQMessage with id " + messageId + " to database: " + e.getMessage(), e); - } - } - - /** - * Stores message meta-data. - * - * @param tx The transaction for the operation. - * @param messageId The message to store the data for. - * @param messageMetaData The message meta data to store. - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - private void storeMetaData(final com.sleepycat.je.Transaction tx, long messageId, - StorableMessageMetaData messageMetaData) - throws AMQStoreException - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("storeMetaData called for transaction " + tx - + ", messageId " + messageId - + ", messageMetaData " + messageMetaData); - } - - DatabaseEntry key = new DatabaseEntry(); - LongBinding.longToEntry(messageId, key); - DatabaseEntry value = new DatabaseEntry(); - - MessageMetaDataBinding messageBinding = MessageMetaDataBinding.getInstance(); - messageBinding.objectToEntry(messageMetaData, value); - try - { - _messageMetaDataDb.put(tx, key, value); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Storing message metadata for message id " + messageId + " in transaction " + tx); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing message metadata with id " + messageId + " to database: " + e.getMessage(), e); - } - } - - /** - * Retrieves message meta-data. - * - * @param messageId The message to get the meta-data for. - * - * @return The message meta data. - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - public StorableMessageMetaData getMessageMetaData(long messageId) throws AMQStoreException - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("public MessageMetaData getMessageMetaData(Long messageId = " - + messageId + "): called"); - } - - DatabaseEntry key = new DatabaseEntry(); - LongBinding.longToEntry(messageId, key); - DatabaseEntry value = new DatabaseEntry(); - MessageMetaDataBinding messageBinding = MessageMetaDataBinding.getInstance(); - - try - { - OperationStatus status = _messageMetaDataDb.get(null, key, value, LockMode.READ_UNCOMMITTED); - if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Metadata not found for message with id " + messageId); - } - - StorableMessageMetaData mdd = messageBinding.entryToObject(value); - - return mdd; - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error reading message metadata for message with id " + messageId + ": " + e.getMessage(), e); - } - } - - /** - * Fills the provided ByteBuffer with as much content for the specified message as possible, starting - * from the specified offset in the message. - * - * @param messageId The message to get the data for. - * @param offset The offset of the data within the message. - * @param dst The destination of the content read back - * - * @return The number of bytes inserted into the destination - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - public int getContent(long messageId, int offset, ByteBuffer dst) throws AMQStoreException - { - DatabaseEntry contentKeyEntry = new DatabaseEntry(); - LongBinding.longToEntry(messageId, contentKeyEntry); - DatabaseEntry value = new DatabaseEntry(); - ContentBinding contentTupleBinding = ContentBinding.getInstance(); - - - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Message Id: " + messageId + " Getting content body from offset: " + offset); - } - - try - { - - int written = 0; - OperationStatus status = _messageContentDb.get(null, contentKeyEntry, value, LockMode.READ_UNCOMMITTED); - if (status == OperationStatus.SUCCESS) - { - byte[] dataAsBytes = contentTupleBinding.entryToObject(value); - int size = dataAsBytes.length; - if (offset > size) - { - throw new RuntimeException("Offset " + offset + " is greater than message size " + size - + " for message id " + messageId + "!"); - - } - - written = size - offset; - if(written > dst.remaining()) - { - written = dst.remaining(); - } - - dst.put(dataAsBytes, offset, written); - } - return written; - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error getting AMQMessage with id " + messageId + " to database: " + e.getMessage(), e); - } - } - - public boolean isPersistent() - { - return true; - } - - @SuppressWarnings("unchecked") - public <T extends StorableMessageMetaData> StoredMessage<T> addMessage(T metaData) - { - if(metaData.isPersistent()) - { - return (StoredMessage<T>) new StoredBDBMessage(getNewMessageId(), metaData); - } - else - { - return new StoredMemoryMessage(getNewMessageId(), metaData); - } - } - - //Package getters for the various databases used by the Store - - Database getMetaDataDb() - { - return _messageMetaDataDb; - } - - Database getContentDb() - { - return _messageContentDb; - } - - Database getDeliveryDb() - { - return _deliveryDb; - } - - /** - * Makes the specified configured object persistent. - * - * @param configuredObject Details of the configured object to store. - * @throws AMQStoreException If the operation fails for any reason. - */ - private void storeConfiguredObjectEntry(ConfiguredObjectRecord configuredObject) throws AMQStoreException - { - if (_stateManager.isInState(State.ACTIVE)) - { - LOGGER.debug("Storing configured object: " + configuredObject); - DatabaseEntry key = new DatabaseEntry(); - UUIDTupleBinding keyBinding = UUIDTupleBinding.getInstance(); - keyBinding.objectToEntry(configuredObject.getId(), key); - - DatabaseEntry value = new DatabaseEntry(); - ConfiguredObjectBinding queueBinding = ConfiguredObjectBinding.getInstance(); - - queueBinding.objectToEntry(configuredObject, value); - try - { - OperationStatus status = _configuredObjectsDb.put(null, key, value); - if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Error writing configured object " + configuredObject + " to database: " - + status); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing configured object " + configuredObject - + " to database: " + e.getMessage(), e); - } - } - } - - private OperationStatus removeConfiguredObject(Transaction tx, UUID id) throws AMQStoreException - { - - LOGGER.debug("Removing configured object: " + id); - DatabaseEntry key = new DatabaseEntry(); - UUIDTupleBinding uuidBinding = UUIDTupleBinding.getInstance(); - uuidBinding.objectToEntry(id, key); - try - { - return _configuredObjectsDb.delete(tx, key); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error deleting of configured object with id " + id + " from database", e); - } - } - - protected abstract StoreFuture commit(com.sleepycat.je.Transaction tx, boolean syncCommit) throws DatabaseException; - - - private class StoredBDBMessage implements StoredMessage<StorableMessageMetaData> - { - - private final long _messageId; - private final boolean _isRecovered; - - private StorableMessageMetaData _metaData; - private volatile SoftReference<StorableMessageMetaData> _metaDataRef; - - private byte[] _data; - private volatile SoftReference<byte[]> _dataRef; - - StoredBDBMessage(long messageId, StorableMessageMetaData metaData) - { - this(messageId, metaData, false); - } - - StoredBDBMessage(long messageId, StorableMessageMetaData metaData, boolean isRecovered) - { - _messageId = messageId; - _isRecovered = isRecovered; - - if(!_isRecovered) - { - _metaData = metaData; - } - _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); - } - - public StorableMessageMetaData getMetaData() - { - StorableMessageMetaData metaData = _metaDataRef.get(); - if(metaData == null) - { - try - { - metaData = AbstractBDBMessageStore.this.getMessageMetaData(_messageId); - } - catch (AMQStoreException e) - { - throw new RuntimeException(e); - } - _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); - } - - return metaData; - } - - public long getMessageNumber() - { - return _messageId; - } - - public void addContent(int offsetInMessage, java.nio.ByteBuffer src) - { - src = src.slice(); - - if(_data == null) - { - _data = new byte[src.remaining()]; - _dataRef = new SoftReference<byte[]>(_data); - src.duplicate().get(_data); - } - else - { - byte[] oldData = _data; - _data = new byte[oldData.length + src.remaining()]; - _dataRef = new SoftReference<byte[]>(_data); - - System.arraycopy(oldData,0,_data,0,oldData.length); - src.duplicate().get(_data, oldData.length, src.remaining()); - } - - } - - public int getContent(int offsetInMessage, java.nio.ByteBuffer dst) - { - byte[] data = _dataRef == null ? null : _dataRef.get(); - if(data != null) - { - int length = Math.min(dst.remaining(), data.length - offsetInMessage); - dst.put(data, offsetInMessage, length); - return length; - } - else - { - try - { - return AbstractBDBMessageStore.this.getContent(_messageId, offsetInMessage, dst); - } - catch (AMQStoreException e) - { - // TODO maybe should throw a checked exception, or at least log before throwing - throw new RuntimeException(e); - } - } - } - - public ByteBuffer getContent(int offsetInMessage, int size) - { - byte[] data = _dataRef == null ? null : _dataRef.get(); - if(data != null) - { - return ByteBuffer.wrap(data,offsetInMessage,size); - } - else - { - ByteBuffer buf = ByteBuffer.allocate(size); - int length = getContent(offsetInMessage, buf); - buf.limit(length); - buf.position(0); - return buf; - } - } - - synchronized void store(com.sleepycat.je.Transaction txn) - { - if (!stored()) - { - try - { - _dataRef = new SoftReference<byte[]>(_data); - AbstractBDBMessageStore.this.storeMetaData(txn, _messageId, _metaData); - AbstractBDBMessageStore.this.addContent(txn, _messageId, 0, - _data == null ? ByteBuffer.allocate(0) : ByteBuffer.wrap(_data)); - } - catch(DatabaseException e) - { - throw new RuntimeException(e); - } - catch (AMQStoreException e) - { - throw new RuntimeException(e); - } - catch (RuntimeException e) - { - LOGGER.error("RuntimeException during store", e); - throw e; - } - finally - { - _metaData = null; - _data = null; - } - } - } - - public synchronized StoreFuture flushToStore() - { - if(!stored()) - { - com.sleepycat.je.Transaction txn = _environment.beginTransaction(null, null); - store(txn); - AbstractBDBMessageStore.this.commit(txn,true); - storedSizeChange(getMetaData().getContentSize()); - } - return StoreFuture.IMMEDIATE_FUTURE; - } - - public void remove() - { - try - { - int delta = getMetaData().getContentSize(); - AbstractBDBMessageStore.this.removeMessage(_messageId, false); - storedSizeChange(-delta); - - } - catch (AMQStoreException e) - { - throw new RuntimeException(e); - } - } - - private boolean stored() - { - return _metaData == null || _isRecovered; - } - } - - private class BDBTransaction implements org.apache.qpid.server.store.Transaction - { - private com.sleepycat.je.Transaction _txn; - private int _storeSizeIncrease; - - private BDBTransaction() - { - try - { - _txn = _environment.beginTransaction(null, null); - } - catch (DatabaseException e) - { - LOGGER.error("Exception during transaction begin, closing store environment.", e); - closeEnvironmentSafely(); - - throw new RuntimeException("Exception during transaction begin, store environment closed.", e); - } - } - - public void enqueueMessage(TransactionLogResource queue, EnqueueableMessage message) throws AMQStoreException - { - if(message.getStoredMessage() instanceof StoredBDBMessage) - { - final StoredBDBMessage storedMessage = (StoredBDBMessage) message.getStoredMessage(); - storedMessage.store(_txn); - _storeSizeIncrease += storedMessage.getMetaData().getContentSize(); - } - - AbstractBDBMessageStore.this.enqueueMessage(_txn, queue, message.getMessageNumber()); - } - - public void dequeueMessage(TransactionLogResource queue, EnqueueableMessage message) throws AMQStoreException - { - AbstractBDBMessageStore.this.dequeueMessage(_txn, queue, message.getMessageNumber()); - } - - public void commitTran() throws AMQStoreException - { - AbstractBDBMessageStore.this.commitTranImpl(_txn, true); - AbstractBDBMessageStore.this.storedSizeChange(_storeSizeIncrease); - } - - public StoreFuture commitTranAsync() throws AMQStoreException - { - AbstractBDBMessageStore.this.storedSizeChange(_storeSizeIncrease); - return AbstractBDBMessageStore.this.commitTranImpl(_txn, false); - } - - public void abortTran() throws AMQStoreException - { - AbstractBDBMessageStore.this.abortTran(_txn); - } - - public void removeXid(long format, byte[] globalId, byte[] branchId) throws AMQStoreException - { - AbstractBDBMessageStore.this.removeXid(_txn, format, globalId, branchId); - } - - public void recordXid(long format, byte[] globalId, byte[] branchId, Record[] enqueues, - Record[] dequeues) throws AMQStoreException - { - AbstractBDBMessageStore.this.recordXid(_txn, format, globalId, branchId, enqueues, dequeues); - } - } - - private void storedSizeChange(final int delta) - { - if(getPersistentSizeHighThreshold() > 0) - { - synchronized (this) - { - // the delta supplied is an approximation of a store size change. we don;t want to check the statistic every - // time, so we do so only when there's been enough change that it is worth looking again. We do this by - // assuming the total size will change by less than twice the amount of the message data change. - long newSize = _totalStoreSize += 2*delta; - - if(!_limitBusted && newSize > getPersistentSizeHighThreshold()) - { - _totalStoreSize = getSizeOnDisk(); - - if(_totalStoreSize > getPersistentSizeHighThreshold()) - { - _limitBusted = true; - _eventManager.notifyEvent(Event.PERSISTENT_MESSAGE_SIZE_OVERFULL); - } - } - else if(_limitBusted && newSize < getPersistentSizeLowThreshold()) - { - long oldSize = _totalStoreSize; - _totalStoreSize = getSizeOnDisk(); - - if(oldSize <= _totalStoreSize) - { - - reduceSizeOnDisk(); - - _totalStoreSize = getSizeOnDisk(); - - } - - if(_totalStoreSize < getPersistentSizeLowThreshold()) - { - _limitBusted = false; - _eventManager.notifyEvent(Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); - } - - - } - } - } - } - - private void reduceSizeOnDisk() - { - _environment.getConfig().setConfigParam(EnvironmentConfig.ENV_RUN_CLEANER, "false"); - boolean cleaned = false; - while (_environment.cleanLog() > 0) - { - cleaned = true; - } - if (cleaned) - { - CheckpointConfig force = new CheckpointConfig(); - force.setForce(true); - _environment.checkpoint(force); - } - - - _environment.getConfig().setConfigParam(EnvironmentConfig.ENV_RUN_CLEANER, "true"); - } - - private long getSizeOnDisk() - { - return _environment.getStats(null).getTotalLogSize(); - } - - private long getPersistentSizeLowThreshold() - { - return _persistentSizeLowThreshold; - } - - private long getPersistentSizeHighThreshold() - { - return _persistentSizeHighThreshold; - } - - private void setEnvironmentConfigProperties(EnvironmentConfig envConfig) - { - for (Map.Entry<String, String> configItem : _envConfigMap.entrySet()) - { - LOGGER.debug("Setting EnvironmentConfig key " + configItem.getKey() + " to '" + configItem.getValue() + "'"); - envConfig.setConfigParam(configItem.getKey(), configItem.getValue()); - } - } - - protected EnvironmentConfig createEnvironmentConfig() - { - EnvironmentConfig envConfig = new EnvironmentConfig(); - envConfig.setAllowCreate(true); - envConfig.setTransactional(true); - - setEnvironmentConfigProperties(envConfig); - - envConfig.setExceptionListener(new LoggingAsyncExceptionListener()); - - return envConfig; - } - - protected void closeEnvironmentSafely() - { - try - { - _environment.close(); - } - catch (DatabaseException ex) - { - LOGGER.error("Exception closing store environment", ex); - } - catch (IllegalStateException ex) - { - LOGGER.error("Exception closing store environment", ex); - } - } - - - private class LoggingAsyncExceptionListener implements ExceptionListener - { - @Override - public void exceptionThrown(ExceptionEvent event) - { - LOGGER.error("Asynchronous exception thrown by BDB thread '" - + event.getThreadName() + "'", event.getException()); - } - } - - @Override - public void onDelete() - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Deleting store " + _storeLocation); - } - - if (_storeLocation != null) - { - File location = new File(_storeLocation); - if (location.exists()) - { - if (!FileUtils.delete(location, true)) - { - LOGGER.error("Cannot delete " + _storeLocation); - } - } - } - } -} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBBackup.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBBackup.java index 9b97fec479..5a498470fb 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBBackup.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBBackup.java @@ -22,7 +22,6 @@ package org.apache.qpid.server.store.berkeleydb; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.Environment; -import com.sleepycat.je.EnvironmentConfig; import com.sleepycat.je.util.DbBackup; import org.apache.log4j.Logger; @@ -336,17 +335,4 @@ public class BDBBackup return backedUpFileNames.toArray(new String[backedUpFileNames.size()]); } - /* - * Creates an environment for the bdb log files in the specified directory. This envrinonment can only be used - * to backup these files, if they are not locked by another database instance. - * - * @param fromdir The path to the directory to create the environment for. - * - * @throws DatabaseException Any underlying exceptions from BDB are allowed to fall through. - */ - private Environment createSourceDirEnvironment(String fromdir) throws DatabaseException - { - // Initialize the BDB backup utility on the source directory. - return new Environment(new File(fromdir), new EnvironmentConfig()); - } } diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAMessageStore.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAMessageStore.java deleted file mode 100644 index fb1dc1f1d3..0000000000 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAMessageStore.java +++ /dev/null @@ -1,658 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.qpid.server.store.berkeleydb; - -import java.io.File; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import org.apache.commons.configuration.Configuration; -import org.apache.commons.configuration.ConfigurationException; -import org.apache.log4j.Logger; -import org.apache.qpid.AMQStoreException; -import org.apache.qpid.server.logging.RootMessageLogger; -import org.apache.qpid.server.logging.actors.AbstractActor; -import org.apache.qpid.server.logging.actors.CurrentActor; -import org.apache.qpid.server.model.VirtualHost; -import org.apache.qpid.server.store.HAMessageStore; -import org.apache.qpid.server.store.MessageStore; -import org.apache.qpid.server.store.MessageStoreRecoveryHandler; -import org.apache.qpid.server.store.State; -import org.apache.qpid.server.store.StoreFuture; -import org.apache.qpid.server.store.TransactionLogRecoveryHandler; - -import com.sleepycat.je.DatabaseException; -import com.sleepycat.je.Durability; -import com.sleepycat.je.Durability.ReplicaAckPolicy; -import com.sleepycat.je.Durability.SyncPolicy; -import com.sleepycat.je.Environment; -import com.sleepycat.je.EnvironmentConfig; -import com.sleepycat.je.OperationFailureException; -import com.sleepycat.je.Transaction; -import com.sleepycat.je.rep.InsufficientLogException; -import com.sleepycat.je.rep.NetworkRestore; -import com.sleepycat.je.rep.NetworkRestoreConfig; -import com.sleepycat.je.rep.ReplicatedEnvironment; -import com.sleepycat.je.rep.ReplicationConfig; -import com.sleepycat.je.rep.ReplicationMutableConfig; -import com.sleepycat.je.rep.ReplicationNode; -import com.sleepycat.je.rep.StateChangeEvent; -import com.sleepycat.je.rep.StateChangeListener; -import com.sleepycat.je.rep.util.ReplicationGroupAdmin; - -public class BDBHAMessageStore extends AbstractBDBMessageStore implements HAMessageStore -{ - private static final Logger LOGGER = Logger.getLogger(BDBHAMessageStore.class); - - private static final Durability DEFAULT_DURABILITY = new Durability(SyncPolicy.NO_SYNC, SyncPolicy.NO_SYNC, ReplicaAckPolicy.SIMPLE_MAJORITY); - - public static final String GRP_MEM_COL_NODE_HOST_PORT = "NodeHostPort"; - public static final String GRP_MEM_COL_NODE_NAME = "NodeName"; - - @SuppressWarnings("serial") - private static final Map<String, String> REPCONFIG_DEFAULTS = Collections.unmodifiableMap(new HashMap<String, String>() - {{ - /** - * Parameter decreased as the 24h default may lead very large log files for most users. - */ - put(ReplicationConfig.REP_STREAM_TIMEOUT, "1 h"); - /** - * Parameter increased as the 5 s default may lead to spurious timeouts. - */ - put(ReplicationConfig.REPLICA_ACK_TIMEOUT, "15 s"); - /** - * Parameter increased as the 10 s default may lead to spurious timeouts. - */ - put(ReplicationConfig.INSUFFICIENT_REPLICAS_TIMEOUT, "20 s"); - /** - * Parameter increased as the 10 h default may cause user confusion. - */ - put(ReplicationConfig.ENV_SETUP_TIMEOUT, "15 min"); - /** - * Parameter changed from default true so we adopt immediately adopt the new behaviour early. False - * is scheduled to become default after JE 5.0.48. - */ - put(ReplicationConfig.PROTOCOL_OLD_STRING_ENCODING, Boolean.FALSE.toString()); - /** - * Parameter decreased as a default 5min interval may lead to bigger data losses on Node - * with NO_SYN durability in case if such Node crushes. - */ - put(ReplicationConfig.LOG_FLUSH_TASK_INTERVAL, "1 min"); - }}); - - public static final String TYPE = "BDB-HA"; - - private String _groupName; - private String _nodeName; - private String _nodeHostPort; - private String _helperHostPort; - private Durability _durability; - - private String _name; - - private CommitThreadWrapper _commitThreadWrapper; - private boolean _coalescingSync; - private boolean _designatedPrimary; - private Map<String, String> _repConfig; - - @Override - public void configure(VirtualHost virtualHost) throws Exception - { - //Mandatory configuration - _groupName = getValidatedStringAttribute(virtualHost, "haGroupName"); - _nodeName = getValidatedStringAttribute(virtualHost, "haNodeName"); - _nodeHostPort = getValidatedStringAttribute(virtualHost, "haNodeAddress"); - _helperHostPort = getValidatedStringAttribute(virtualHost, "haHelperAddress"); - _name = virtualHost.getName(); - - //Optional configuration - String durabilitySetting = getStringAttribute(virtualHost,"haDurability",null); - if (durabilitySetting == null) - { - _durability = DEFAULT_DURABILITY; - } - else - { - _durability = Durability.parse(durabilitySetting); - } - _designatedPrimary = getBooleanAttribute(virtualHost, "haDesignatedPrimary", Boolean.FALSE); - _coalescingSync = getBooleanAttribute(virtualHost, "haCoalescingSync", Boolean.TRUE); - - _repConfig = new HashMap<String, String>(REPCONFIG_DEFAULTS); - Object repConfigAttr = virtualHost.getAttribute("haReplicationConfig"); - if(repConfigAttr instanceof Map) - { - _repConfig.putAll((Map)repConfigAttr); - } - - if (_coalescingSync && _durability.getLocalSync() == SyncPolicy.SYNC) - { - throw new ConfigurationException("Coalescing sync cannot be used with master sync policy " + SyncPolicy.SYNC - + "! Please set highAvailability.coalescingSync to false in store configuration."); - } - - super.configure(virtualHost); - } - - - private String getValidatedStringAttribute(org.apache.qpid.server.model.VirtualHost virtualHost, String attributeName) - throws ConfigurationException - { - Object attrValue = virtualHost.getAttribute(attributeName); - if(attrValue != null) - { - return attrValue.toString(); - } - else - { - throw new ConfigurationException("BDB HA configuration key not found. Please specify configuration attribute: " - + attributeName); - } - } - - private String getStringAttribute(org.apache.qpid.server.model.VirtualHost virtualHost, String attributeName, String defaultVal) - { - Object attrValue = virtualHost.getAttribute(attributeName); - if(attrValue != null) - { - return attrValue.toString(); - } - return defaultVal; - } - - private boolean getBooleanAttribute(org.apache.qpid.server.model.VirtualHost virtualHost, String attributeName, boolean defaultVal) - { - Object attrValue = virtualHost.getAttribute(attributeName); - if(attrValue != null) - { - if(attrValue instanceof Boolean) - { - return ((Boolean) attrValue).booleanValue(); - } - else if(attrValue instanceof String) - { - return Boolean.parseBoolean((String)attrValue); - } - - } - return defaultVal; - } - - - @Override - protected void setupStore(File storePath, String name) throws DatabaseException, AMQStoreException - { - super.setupStore(storePath, name); - - if(_coalescingSync) - { - _commitThreadWrapper = new CommitThreadWrapper("Commit-Thread-" + name, getEnvironment()); - _commitThreadWrapper.startCommitThread(); - } - } - - @Override - protected Environment createEnvironment(File environmentPath) throws DatabaseException - { - if (LOGGER.isInfoEnabled()) - { - LOGGER.info("Environment path " + environmentPath.getAbsolutePath()); - LOGGER.info("Group name " + _groupName); - LOGGER.info("Node name " + _nodeName); - LOGGER.info("Node host port " + _nodeHostPort); - LOGGER.info("Helper host port " + _helperHostPort); - LOGGER.info("Durability " + _durability); - LOGGER.info("Coalescing sync " + _coalescingSync); - LOGGER.info("Designated primary (applicable to 2 node case only) " + _designatedPrimary); - } - - final ReplicationConfig replicationConfig = new ReplicationConfig(_groupName, _nodeName, _nodeHostPort); - - replicationConfig.setHelperHosts(_helperHostPort); - replicationConfig.setDesignatedPrimary(_designatedPrimary); - setReplicationConfigProperties(replicationConfig); - - final EnvironmentConfig envConfig = createEnvironmentConfig(); - envConfig.setDurability(_durability); - - ReplicatedEnvironment replicatedEnvironment = null; - try - { - replicatedEnvironment = new ReplicatedEnvironment(environmentPath, replicationConfig, envConfig); - } - catch (final InsufficientLogException ile) - { - LOGGER.info("InsufficientLogException thrown and so full network restore required", ile); - NetworkRestore restore = new NetworkRestore(); - NetworkRestoreConfig config = new NetworkRestoreConfig(); - config.setRetainLogFiles(false); - restore.execute(ile, config); - replicatedEnvironment = new ReplicatedEnvironment(environmentPath, replicationConfig, envConfig); - } - - return replicatedEnvironment; - } - - @Override - public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler messageRecoveryHandler, - TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception - { - super.configureMessageStore(virtualHost, messageRecoveryHandler, tlogRecoveryHandler); - - final ReplicatedEnvironment replicatedEnvironment = getReplicatedEnvironment(); - - replicatedEnvironment.setStateChangeListener(new BDBHAMessageStoreStateChangeListener()); - } - - @Override - public synchronized void activate() throws Exception - { - // Before proceeding, perform a log flush with an fsync - getEnvironment().flushLog(true); - - super.activate(); - } - - @Override - public synchronized void passivate() - { - if (_stateManager.isNotInState(State.INITIALISED)) - { - LOGGER.debug("Store becoming passive"); - _stateManager.attainState(State.INITIALISED); - } - } - - public String getName() - { - return _name; - } - - public String getGroupName() - { - return _groupName; - } - - public String getNodeName() - { - return _nodeName; - } - - public String getNodeHostPort() - { - return _nodeHostPort; - } - - public String getHelperHostPort() - { - return _helperHostPort; - } - - public String getDurability() - { - return _durability.toString(); - } - - public boolean isCoalescingSync() - { - return _coalescingSync; - } - - public String getNodeState() - { - ReplicatedEnvironment.State state = getReplicatedEnvironment().getState(); - return state.toString(); - } - - public Boolean isDesignatedPrimary() - { - return getReplicatedEnvironment().getRepMutableConfig().getDesignatedPrimary(); - } - - public List<Map<String, String>> getGroupMembers() - { - List<Map<String, String>> members = new ArrayList<Map<String,String>>(); - - for (ReplicationNode node : getReplicatedEnvironment().getGroup().getNodes()) - { - Map<String, String> nodeMap = new HashMap<String, String>(); - nodeMap.put(BDBHAMessageStore.GRP_MEM_COL_NODE_NAME, node.getName()); - nodeMap.put(BDBHAMessageStore.GRP_MEM_COL_NODE_HOST_PORT, node.getHostName() + ":" + node.getPort()); - members.add(nodeMap); - } - - return members; - } - - public void removeNodeFromGroup(String nodeName) throws AMQStoreException - { - try - { - createReplicationGroupAdmin().removeMember(nodeName); - } - catch (OperationFailureException ofe) - { - throw new AMQStoreException("Failed to remove '" + nodeName + "' from group. " + ofe.getMessage(), ofe); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Failed to remove '" + nodeName + "' from group. " + e.getMessage(), e); - } - } - - public void setDesignatedPrimary(boolean isPrimary) throws AMQStoreException - { - try - { - final ReplicatedEnvironment replicatedEnvironment = getReplicatedEnvironment(); - synchronized(replicatedEnvironment) - { - final ReplicationMutableConfig oldConfig = replicatedEnvironment.getRepMutableConfig(); - final ReplicationMutableConfig newConfig = oldConfig.setDesignatedPrimary(isPrimary); - replicatedEnvironment.setRepMutableConfig(newConfig); - } - - if (LOGGER.isInfoEnabled()) - { - LOGGER.info("Node " + _nodeName + " successfully set as designated primary for group"); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Failed to set '" + _nodeName + "' as designated primary for group. " + e.getMessage(), e); - } - } - - ReplicatedEnvironment getReplicatedEnvironment() - { - return (ReplicatedEnvironment)getEnvironment(); - } - - public void updateAddress(String nodeName, String newHostName, int newPort) throws AMQStoreException - { - try - { - createReplicationGroupAdmin().updateAddress(nodeName, newHostName, newPort); - } - catch (OperationFailureException ofe) - { - throw new AMQStoreException("Failed to update address for '" + nodeName + - "' with new host " + newHostName + " and new port " + newPort + ". " + ofe.getMessage(), ofe); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Failed to update address for '" + nodeName + - "' with new host " + newHostName + " and new port " + newPort + ". " + e.getMessage(), e); - } - } - - @Override - protected StoreFuture commit(Transaction tx, boolean syncCommit) throws DatabaseException - { - // Using commit() instead of commitNoSync() for the HA store to allow - // the HA durability configuration to influence resulting behaviour. - try - { - tx.commit(); - } - catch (DatabaseException de) - { - LOGGER.error("Got DatabaseException on commit, closing environment", de); - - closeEnvironmentSafely(); - - throw de; - } - - if(_coalescingSync) - { - return _commitThreadWrapper.commit(tx, syncCommit); - } - else - { - return StoreFuture.IMMEDIATE_FUTURE; - } - } - - @Override - protected void closeInternal() throws Exception - { - substituteNoOpStateChangeListenerOn(getReplicatedEnvironment()); - - try - { - if(_coalescingSync) - { - _commitThreadWrapper.stopCommitThread(); - } - } - finally - { - super.closeInternal(); - } - } - - /** - * Replicas emit a state change event {@link com.sleepycat.je.rep.ReplicatedEnvironment.State#DETACHED} during - * {@link Environment#close()}. We replace the StateChangeListener so we silently ignore this state change. - */ - private void substituteNoOpStateChangeListenerOn(ReplicatedEnvironment replicatedEnvironment) - { - LOGGER.debug("Substituting no-op state change listener for environment close"); - replicatedEnvironment.setStateChangeListener(new NoOpStateChangeListener()); - } - - private ReplicationGroupAdmin createReplicationGroupAdmin() - { - final Set<InetSocketAddress> helpers = new HashSet<InetSocketAddress>(); - helpers.addAll(getReplicatedEnvironment().getRepConfig().getHelperSockets()); - - final ReplicationConfig repConfig = getReplicatedEnvironment().getRepConfig(); - helpers.add(InetSocketAddress.createUnresolved(repConfig.getNodeHostname(), repConfig.getNodePort())); - - return new ReplicationGroupAdmin(_groupName, helpers); - } - - - private void setReplicationConfigProperties(ReplicationConfig replicationConfig) - { - for (Map.Entry<String, String> configItem : _repConfig.entrySet()) - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Setting ReplicationConfig key " + configItem.getKey() + " to '" + configItem.getValue() + "'"); - } - replicationConfig.setConfigParam(configItem.getKey(), configItem.getValue()); - } - } - - private String getValidatedPropertyFromConfig(String key, Configuration config) throws ConfigurationException - { - if (!config.containsKey(key)) - { - throw new ConfigurationException("BDB HA configuration key not found. Please specify configuration key with XPath: " - + key.replace('.', '/')); - } - return config.getString(key); - } - - private class BDBHAMessageStoreStateChangeListener implements StateChangeListener - { - private final Executor _executor = Executors.newSingleThreadExecutor(); - - @Override - public void stateChange(StateChangeEvent stateChangeEvent) throws RuntimeException - { - com.sleepycat.je.rep.ReplicatedEnvironment.State state = stateChangeEvent.getState(); - - if (LOGGER.isInfoEnabled()) - { - LOGGER.info("Received BDB event indicating transition to state " + state); - } - - switch (state) - { - case MASTER: - activateStoreAsync(); - break; - case REPLICA: - passivateStoreAsync(); - break; - case DETACHED: - LOGGER.error("BDB replicated node in detached state, therefore passivating."); - passivateStoreAsync(); - break; - case UNKNOWN: - LOGGER.warn("BDB replicated node in unknown state (hopefully temporarily)"); - break; - default: - LOGGER.error("Unexpected state change: " + state); - throw new IllegalStateException("Unexpected state change: " + state); - } - } - - /** - * Calls {@link MessageStore#activate()}. - * - * <p/> - * - * This is done a background thread, in line with - * {@link StateChangeListener#stateChange(StateChangeEvent)}'s JavaDoc, because - * activate may execute transactions, which can't complete until - * {@link StateChangeListener#stateChange(StateChangeEvent)} has returned. - */ - private void activateStoreAsync() - { - String threadName = "BDBHANodeActivationThread-" + _name; - executeStateChangeAsync(new Callable<Void>() - { - @Override - public Void call() throws Exception - { - try - { - activate(); - } - catch (Exception e) - { - LOGGER.error("Failed to activate on hearing MASTER change event",e); - throw e; - } - return null; - } - }, threadName); - } - - /** - * Calls {@link #passivate()}. - * - * <p/> - * This is done a background thread, in line with - * {@link StateChangeListener#stateChange(StateChangeEvent)}'s JavaDoc, because - * passivation due to the effect of state change listeners. - */ - private void passivateStoreAsync() - { - String threadName = "BDBHANodePassivationThread-" + _name; - executeStateChangeAsync(new Callable<Void>() - { - - @Override - public Void call() throws Exception - { - try - { - passivate(); - } - catch (Exception e) - { - LOGGER.error("Failed to passivate on hearing REPLICA or DETACHED change event",e); - throw e; - } - return null; - } - }, threadName); - } - - private void executeStateChangeAsync(final Callable<Void> callable, final String threadName) - { - final RootMessageLogger _rootLogger = CurrentActor.get().getRootMessageLogger(); - - _executor.execute(new Runnable() - { - - @Override - public void run() - { - final String originalThreadName = Thread.currentThread().getName(); - Thread.currentThread().setName(threadName); - try - { - CurrentActor.set(new AbstractActor(_rootLogger) - { - @Override - public String getLogMessage() - { - return threadName; - } - }); - - try - { - callable.call(); - } - catch (Exception e) - { - LOGGER.error("Exception during state change", e); - } - } - finally - { - Thread.currentThread().setName(originalThreadName); - } - } - }); - } - } - - private class NoOpStateChangeListener implements StateChangeListener - { - @Override - public void stateChange(StateChangeEvent stateChangeEvent) - throws RuntimeException - { - } - } - - @Override - public String getStoreType() - { - return TYPE; - } -} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAVirtualHost.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAVirtualHost.java index bb3c7b108d..52bb5e574b 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAVirtualHost.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAVirtualHost.java @@ -20,9 +20,13 @@ package org.apache.qpid.server.store.berkeleydb; * */ +import java.util.UUID; + +import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.VirtualHostConfiguration; import org.apache.qpid.server.connection.IConnectionRegistry; import org.apache.qpid.server.logging.subjects.MessageStoreLogSubject; +import org.apache.qpid.server.model.UUIDGenerator; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.store.DurableConfigurationRecoverer; @@ -31,15 +35,22 @@ import org.apache.qpid.server.store.Event; import org.apache.qpid.server.store.EventListener; import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.store.OperationalLoggingListener; +import org.apache.qpid.server.store.berkeleydb.replication.ReplicatedEnvironmentFacade; +import org.apache.qpid.server.store.berkeleydb.replication.ReplicatedEnvironmentFacadeFactory; import org.apache.qpid.server.virtualhost.AbstractVirtualHost; import org.apache.qpid.server.virtualhost.DefaultUpgraderProvider; import org.apache.qpid.server.virtualhost.State; import org.apache.qpid.server.virtualhost.VirtualHostConfigRecoveryHandler; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import com.sleepycat.je.rep.StateChangeEvent; +import com.sleepycat.je.rep.StateChangeListener; + public class BDBHAVirtualHost extends AbstractVirtualHost { - private BDBHAMessageStore _messageStore; + private static final Logger LOGGER = Logger.getLogger(BDBHAVirtualHost.class); + + private BDBMessageStore _messageStore; private boolean _inVhostInitiatedClose; @@ -57,18 +68,15 @@ public class BDBHAVirtualHost extends AbstractVirtualHost protected void initialiseStorage(VirtualHostConfiguration hostConfig, VirtualHost virtualHost) throws Exception { - _messageStore = new BDBHAMessageStore(); + _messageStore = new BDBMessageStore(new ReplicatedEnvironmentFacadeFactory()); - final MessageStoreLogSubject storeLogSubject = - new MessageStoreLogSubject(getName(), _messageStore.getClass().getSimpleName()); + MessageStoreLogSubject storeLogSubject = new MessageStoreLogSubject(getName(), _messageStore.getClass().getSimpleName()); OperationalLoggingListener.listen(_messageStore, storeLogSubject); _messageStore.addEventListener(new BeforeActivationListener(), Event.BEFORE_ACTIVATE); _messageStore.addEventListener(new AfterActivationListener(), Event.AFTER_ACTIVATE); _messageStore.addEventListener(new BeforeCloseListener(), Event.BEFORE_CLOSE); - - _messageStore.addEventListener(new AfterInitialisationListener(), Event.AFTER_INIT); _messageStore.addEventListener(new BeforePassivationListener(), Event.BEFORE_PASSIVATE); @@ -85,6 +93,11 @@ public class BDBHAVirtualHost extends AbstractVirtualHost virtualHost, recoveryHandler, recoveryHandler ); + + // Make the virtualhost model object a replication group listener + ReplicatedEnvironmentFacade environmentFacade = (ReplicatedEnvironmentFacade) _messageStore.getEnvironmentFacade(); + environmentFacade.setReplicationGroupListener(getReplicationGroupListener()); + environmentFacade.setStateChangeListener(new BDBHAMessageStoreStateChangeListener()); } @@ -122,6 +135,13 @@ public class BDBHAVirtualHost extends AbstractVirtualHost return _messageStore; } + @Override + public UUID getId() + { + //TODO: a temporary approach untill we change the broker model to have Nodes as Broker children + return UUIDGenerator.generateVhostUUID(((ReplicatedEnvironmentFacade) _messageStore.getEnvironmentFacade()).getGroupName()); + } + private final class AfterInitialisationListener implements EventListener { public void event(Event event) @@ -145,12 +165,7 @@ public class BDBHAVirtualHost extends AbstractVirtualHost * is documented as exceptionally rare.. */ - getConnectionRegistry().close(IConnectionRegistry.VHOST_PASSIVATE_REPLY_TEXT); - removeHouseKeepingTasks(); - - getQueueRegistry().stopAllAndUnregisterMBeans(); - getExchangeRegistry().clearAndUnregisterMbeans(); - getDtxRegistry().close(); + passivate(IConnectionRegistry.VHOST_PASSIVATE_REPLY_TEXT); finalState = State.PASSIVE; } @@ -202,4 +217,69 @@ public class BDBHAVirtualHost extends AbstractVirtualHost } } + private class BDBHAMessageStoreStateChangeListener implements StateChangeListener + { + + @Override + public void stateChange(StateChangeEvent stateChangeEvent) throws RuntimeException + { + com.sleepycat.je.rep.ReplicatedEnvironment.State state = stateChangeEvent.getState(); + + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Received BDB event indicating transition to state " + state + + " when current message store state is " + _messageStore._stateManager.getState()); + } + + switch (state) + { + case MASTER: + activate(); + break; + case REPLICA: + passivate(); + break; + case DETACHED: + LOGGER.error("BDB replicated node in detached state, therefore passivating."); + passivate(); + break; + case UNKNOWN: + LOGGER.warn("BDB replicated node in unknown state (hopefully temporarily)"); + break; + default: + LOGGER.error("Unexpected state change: " + state); + throw new IllegalStateException("Unexpected state change: " + state); + } + } + + private void activate() + { + try + { + _messageStore.getEnvironmentFacade().getEnvironment().flushLog(true); + _messageStore.activate(); + } + catch (Exception e) + { + LOGGER.error("Failed to activate on hearing MASTER change event", e); + } + } + + private void passivate() + { + try + { + //TODO: move this this into the store method passivate() + if (_messageStore._stateManager.isNotInState(org.apache.qpid.server.store.State.INITIALISED)) + { + _messageStore._stateManager.attainState(org.apache.qpid.server.store.State.INITIALISED); + } + } + catch (Exception e) + { + LOGGER.error("Failed to passivate on hearing REPLICA or DETACHED change event", e); + } + } + + } } diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAVirtualHostFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAVirtualHostFactory.java index 3b564f33fd..48d863530a 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAVirtualHostFactory.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBHAVirtualHostFactory.java @@ -23,12 +23,18 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.UUID; + import org.apache.commons.configuration.Configuration; import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.UUIDGenerator; import org.apache.qpid.server.model.adapter.VirtualHostAdapter; import org.apache.qpid.server.plugin.VirtualHostFactory; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.store.MessageStoreConstants; +import org.apache.qpid.server.store.berkeleydb.replication.LocalReplicationNode; import org.apache.qpid.server.virtualhost.VirtualHost; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; @@ -60,82 +66,26 @@ public class BDBHAVirtualHostFactory implements VirtualHostFactory @Override public void validateAttributes(Map<String, Object> attributes) { - validateAttribute(org.apache.qpid.server.model.VirtualHost.STORE_PATH, String.class, attributes); - validateAttribute("haGroupName", String.class, attributes); - validateAttribute("haNodeName", String.class, attributes); - validateAttribute("haNodeAddress", String.class, attributes); - validateAttribute("haHelperAddress", String.class, attributes); } - private void validateAttribute(String attrName, Class<?> clazz, Map<String, Object> attributes) - { - Object attr = attributes.get(attrName); - if(!clazz.isInstance(attr)) - { - throw new IllegalArgumentException("Attribute '"+ attrName - +"' is required and must be of type "+clazz.getSimpleName()+"."); - } - } @Override public Map<String, Object> createVirtualHostConfiguration(VirtualHostAdapter virtualHostAdapter) { + //TODO: Dead code? LinkedHashMap<String,Object> convertedMap = new LinkedHashMap<String, Object>(); convertedMap.put("store.environment-path", virtualHostAdapter.getAttribute(org.apache.qpid.server.model.VirtualHost.STORE_PATH)); - return convertedMap; } + @Override public Map<String, Object> convertVirtualHostConfiguration(Configuration configuration) { - LinkedHashMap<String,Object> convertedMap = new LinkedHashMap<String, Object>(); - Configuration storeConfiguration = configuration.subset("store"); - - convertedMap.put(org.apache.qpid.server.model.VirtualHost.STORE_PATH, storeConfiguration.getString(MessageStoreConstants.ENVIRONMENT_PATH_PROPERTY)); convertedMap.put(MessageStoreConstants.OVERFULL_SIZE_ATTRIBUTE, storeConfiguration.getString(MessageStoreConstants.OVERFULL_SIZE_PROPERTY)); convertedMap.put(MessageStoreConstants.UNDERFULL_SIZE_ATTRIBUTE, storeConfiguration.getString(MessageStoreConstants.UNDERFULL_SIZE_PROPERTY)); - convertedMap.put("haGroupName", configuration.getString("store.highAvailability.groupName")); - convertedMap.put("haNodeName", configuration.getString("store.highAvailability.nodeName")); - convertedMap.put("haNodeAddress", configuration.getString("store.highAvailability.nodeHostPort")); - convertedMap.put("haHelperAddress", configuration.getString("store.highAvailability.helperHostPort")); - - final Object haDurability = configuration.getString("store.highAvailability.durability"); - if(haDurability !=null) - { - convertedMap.put("haDurability", haDurability); - } - - final Object designatedPrimary = configuration.getString("store.highAvailability.designatedPrimary"); - if(designatedPrimary!=null) - { - convertedMap.put("haDesignatedPrimary", designatedPrimary); - } - - final Object coalescingSync = configuration.getString("store.highAvailability.coalescingSync"); - if(coalescingSync!=null) - { - convertedMap.put("haCoalescingSync", coalescingSync); - } - - - Map<String, String> attributes = getEnvironmentMap(storeConfiguration, "envConfig"); - - if(!attributes.isEmpty()) - { - convertedMap.put("bdbEnvironmentConfig",attributes); - } - - attributes = getEnvironmentMap(storeConfiguration, "repConfig"); - - if(!attributes.isEmpty()) - { - convertedMap.put("haReplicationConfig",attributes); - } - return convertedMap; - } private Map<String, String> getEnvironmentMap(Configuration storeConfiguration, String configName) @@ -155,4 +105,55 @@ public class BDBHAVirtualHostFactory implements VirtualHostFactory } return attributes; } + + @Override + public ReplicationNode createReplicationNode(Configuration configuration, org.apache.qpid.server.model.VirtualHost virtualHost) + { + Configuration storeConfiguration = configuration.subset("store"); + + String nodeName = storeConfiguration.getString("highAvailability.nodeName"); + String groupName = storeConfiguration.getString("highAvailability.groupName"); + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(ReplicationNode.NAME, nodeName); + attributes.put(ReplicationNode.GROUP_NAME, groupName); + attributes.put(ReplicationNode.HOST_PORT, storeConfiguration.getString("highAvailability.nodeHostPort")); + attributes.put(ReplicationNode.HELPER_HOST_PORT, storeConfiguration.getString("highAvailability.helperHostPort")); + attributes.put(org.apache.qpid.server.model.VirtualHost.STORE_PATH, + storeConfiguration.getString(MessageStoreConstants.ENVIRONMENT_PATH_PROPERTY)); + + String durability = storeConfiguration.getString("highAvailability.durability"); + if (durability != null) + { + attributes.put(ReplicationNode.DURABILITY, durability); + } + + String designatedPrimary = storeConfiguration.getString("highAvailability.designatedPrimary"); + if (designatedPrimary != null) + { + attributes.put(ReplicationNode.DESIGNATED_PRIMARY, designatedPrimary); + } + + String coalescingSync = storeConfiguration.getString("highAvailability.coalescingSync"); + if (coalescingSync != null) + { + attributes.put(ReplicationNode.COALESCING_SYNC, coalescingSync); + } + + Map<String, String> envAttributes = getEnvironmentMap(storeConfiguration, "envConfig"); + if (envAttributes != null && envAttributes.size() > 0) + { + attributes.put(ReplicationNode.PARAMETERS, envAttributes); + } + + Map<String, String> repAttributes = getEnvironmentMap(storeConfiguration, "repConfig"); + if (repAttributes != null && repAttributes.size() > 0) + { + attributes.put(ReplicationNode.REPLICATION_PARAMETERS, repAttributes); + } + + Broker broker = virtualHost.getParent(Broker.class); + UUID uuid = UUIDGenerator.generateReplicationNodeId(groupName, nodeName); + return new LocalReplicationNode(uuid, attributes, virtualHost, broker.getTaskExecutor()); + } + } diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java index 4028de4b80..ab481fe816 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java @@ -20,16 +20,45 @@ */ package org.apache.qpid.server.store.berkeleydb; +import com.sleepycat.bind.tuple.ByteBinding; +import com.sleepycat.bind.tuple.IntegerBinding; +import com.sleepycat.bind.tuple.LongBinding; +import com.sleepycat.je.*; +import com.sleepycat.je.Transaction; + import java.io.File; +import java.lang.ref.SoftReference; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import org.apache.qpid.AMQStoreException; -import org.apache.qpid.server.store.MessageStore; -import org.apache.qpid.server.store.StoreFuture; - -import com.sleepycat.je.DatabaseException; -import com.sleepycat.je.Environment; -import com.sleepycat.je.EnvironmentConfig; +import org.apache.qpid.server.message.EnqueueableMessage; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.*; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler.StoredMessageRecoveryHandler; +import org.apache.qpid.server.store.TransactionLogRecoveryHandler.QueueEntryRecoveryHandler; +import org.apache.qpid.server.store.berkeleydb.entry.PreparedTransaction; +import org.apache.qpid.server.store.berkeleydb.entry.QueueEntryKey; +import org.apache.qpid.server.store.berkeleydb.entry.Xid; +import org.apache.qpid.server.store.berkeleydb.tuple.ConfiguredObjectBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.ContentBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.MessageMetaDataBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.PreparedTransactionBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.QueueEntryBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.UUIDTupleBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.XidBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.Upgrader; +import org.apache.qpid.util.FileUtils; /** * BDBMessageStore implements a persistent {@link MessageStore} using the BDB high performance log. @@ -39,76 +68,1661 @@ import com.sleepycat.je.EnvironmentConfig; * exchanges. <tr><td> Store and remove messages. <tr><td> Bind and unbind queues to exchanges. <tr><td> Enqueue and * dequeue messages to queues. <tr><td> Generate message identifiers. </table> */ -public class BDBMessageStore extends AbstractBDBMessageStore +public class BDBMessageStore implements MessageStore, DurableConfigurationStore { private static final Logger LOGGER = Logger.getLogger(BDBMessageStore.class); - public static final String TYPE = "BDB"; - private CommitThreadWrapper _commitThreadWrapper; + + public static final int VERSION = 7; + public static final String ENVIRONMENT_CONFIGURATION = "bdbEnvironmentConfig"; + + private static final int LOCK_RETRY_ATTEMPTS = 5; + private static String CONFIGURED_OBJECTS_DB_NAME = "CONFIGURED_OBJECTS"; + private static String MESSAGE_META_DATA_DB_NAME = "MESSAGE_METADATA"; + private static String MESSAGE_CONTENT_DB_NAME = "MESSAGE_CONTENT"; + private static String DELIVERY_DB_NAME = "QUEUE_ENTRIES"; + private static String BRIDGEDB_NAME = "BRIDGES"; + private static String LINKDB_NAME = "LINKS"; + private static String XID_DB_NAME = "XIDS"; + private static String CONFIG_VERSION_DB_NAME = "CONFIG_VERSION"; + private static final String[] DATABASE_NAMES = new String[] { CONFIGURED_OBJECTS_DB_NAME, MESSAGE_META_DATA_DB_NAME, + MESSAGE_CONTENT_DB_NAME, DELIVERY_DB_NAME, BRIDGEDB_NAME, LINKDB_NAME, XID_DB_NAME, CONFIG_VERSION_DB_NAME }; + + private final AtomicBoolean _closed = new AtomicBoolean(false); + + private EnvironmentFacade _environmentFacade; + private final AtomicLong _messageId = new AtomicLong(0); + + protected final StateManager _stateManager; + + private MessageStoreRecoveryHandler _messageRecoveryHandler; + + private TransactionLogRecoveryHandler _tlogRecoveryHandler; + + private ConfigurationRecoveryHandler _configRecoveryHandler; + + private long _totalStoreSize; + private boolean _limitBusted; + private long _persistentSizeLowThreshold; + private long _persistentSizeHighThreshold; + + private final EventManager _eventManager = new EventManager(); + private final String _type; + private VirtualHost _virtualHost; + + private final EnvironmentFacadeFactory _environmentFacadeFactory; + + private volatile Committer _committer; + + public BDBMessageStore() + { + this(new StandardEnvironmentFacadeFactory()); + } + + public BDBMessageStore(EnvironmentFacadeFactory environmentFacadeFactory) + { + _type = environmentFacadeFactory.getType();; + _environmentFacadeFactory = environmentFacadeFactory; + _stateManager = new StateManager(_eventManager); + } @Override - protected void setupStore(File storePath, String name) throws DatabaseException, AMQStoreException + public void addEventListener(EventListener eventListener, Event... events) { - super.setupStore(storePath, name); + _eventManager.addEventListener(eventListener, events); + } - _commitThreadWrapper = new CommitThreadWrapper("Commit-Thread-" + name, getEnvironment()); - _commitThreadWrapper.startCommitThread(); + @Override + public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) + { + _stateManager.attainState(State.INITIALISING); + + _configRecoveryHandler = recoveryHandler; + _virtualHost = virtualHost; + } + + @Override + public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler messageRecoveryHandler, + TransactionLogRecoveryHandler tlogRecoveryHandler) throws AMQStoreException + { + if(_stateManager.isInState(State.INITIAL)) + { + // Is acting as a message store, but not a durable config store + _stateManager.attainState(State.INITIALISING); + } + + _messageRecoveryHandler = messageRecoveryHandler; + _tlogRecoveryHandler = tlogRecoveryHandler; + _virtualHost = virtualHost; + + + completeInitialisation(); } - protected Environment createEnvironment(File environmentPath) throws DatabaseException + private void completeInitialisation() throws AMQStoreException { - LOGGER.info("BDB message store using environment path " + environmentPath.getAbsolutePath()); - EnvironmentConfig envConfig = createEnvironmentConfig(); + configure(_virtualHost, _messageRecoveryHandler != null); + _stateManager.attainState(State.INITIALISED); + } + + private void startActivation() throws AMQStoreException + { + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); try { - return new Environment(environmentPath, envConfig); + new Upgrader(_environmentFacade.getEnvironment(), _virtualHost.getName()).upgradeIfNecessary(); + _environmentFacade.openDatabases(dbConfig, DATABASE_NAMES); + _totalStoreSize = getSizeOnDisk(); + } + catch(DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Cannot configure store", e); + } + + } + + @Override + public synchronized void activate() throws AMQStoreException + { + // check if acting as a durable config store, but not a message store + if(_stateManager.isInState(State.INITIALISING)) + { + completeInitialisation(); + } + + _stateManager.attainState(State.ACTIVATING); + startActivation(); + + if(_configRecoveryHandler != null) + { + recoverConfig(_configRecoveryHandler); } - catch (DatabaseException de) + if(_messageRecoveryHandler != null) + { + recoverMessages(_messageRecoveryHandler); + } + if(_tlogRecoveryHandler != null) + { + recoverQueueEntries(_tlogRecoveryHandler); + } + + _stateManager.attainState(State.ACTIVE); + } + + @Override + public org.apache.qpid.server.store.Transaction newTransaction() throws AMQStoreException + { + return new BDBTransaction(); + } + + private void configure(VirtualHost virtualHost, boolean isMessageStore) throws AMQStoreException + { + Object overfullAttr = virtualHost.getAttribute(MessageStoreConstants.OVERFULL_SIZE_ATTRIBUTE); + Object underfullAttr = virtualHost.getAttribute(MessageStoreConstants.UNDERFULL_SIZE_ATTRIBUTE); + + _persistentSizeHighThreshold = overfullAttr == null ? -1l : + overfullAttr instanceof Number ? ((Number) overfullAttr).longValue() : Long.parseLong(overfullAttr.toString()); + _persistentSizeLowThreshold = underfullAttr == null ? _persistentSizeHighThreshold : + underfullAttr instanceof Number ? ((Number) underfullAttr).longValue() : Long.parseLong(underfullAttr.toString()); + + + if(_persistentSizeLowThreshold > _persistentSizeHighThreshold || _persistentSizeLowThreshold < 0l) { - if (de.getMessage().contains("Environment.setAllowCreate is false")) + _persistentSizeLowThreshold = _persistentSizeHighThreshold; + } + + _environmentFacade = _environmentFacadeFactory.createEnvironmentFacade(virtualHost, isMessageStore); + + _committer = _environmentFacade.createCommitter(virtualHost.getName()); + _committer.start(); + } + + @Override + public String getStoreLocation() + { + if (_environmentFacade == null) + { + return null; + } + return _environmentFacade.getStoreLocation(); + } + + public EnvironmentFacade getEnvironmentFacade() + { + return _environmentFacade; + } + + /** + * Called to close and cleanup any resources used by the message store. + * + * @throws Exception If the close fails. + */ + @Override + public void close() throws AMQStoreException + { + if (_closed.compareAndSet(false, true)) + { + _stateManager.attainState(State.CLOSING); + try { - //Allow the creation this time - envConfig.setAllowCreate(true); - return new Environment(environmentPath, envConfig); + try + { + _committer.stop(); + } + finally + { + closeEnvironment(); + } } - else + catch(DatabaseException e) + { + throw new AMQStoreException("Exception occured on message store close", e); + } + _stateManager.attainState(State.CLOSED); + } + } + + private void closeEnvironment() + { + if (_environmentFacade != null) + { + _environmentFacade.close(); + } + } + + private void recoverConfig(ConfigurationRecoveryHandler recoveryHandler) throws AMQStoreException + { + try + { + final int configVersion = getConfigVersion(); + recoveryHandler.beginConfigurationRecovery(this, configVersion); + loadConfiguredObjects(recoveryHandler); + + final int newConfigVersion = recoveryHandler.completeConfigurationRecovery(); + if(newConfigVersion != configVersion) + { + updateConfigVersion(newConfigVersion); + } + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Error recovering persistent state: " + e.getMessage(), e); + } + + } + + @SuppressWarnings("resource") + private void updateConfigVersion(int newConfigVersion) throws AMQStoreException + { + Cursor cursor = null; + try + { + Transaction txn = _environmentFacade.getEnvironment().beginTransaction(null, null); + cursor = getConfigVersionDb().openCursor(txn, null); + DatabaseEntry key = new DatabaseEntry(); + ByteBinding.byteToEntry((byte) 0,key); + DatabaseEntry value = new DatabaseEntry(); + + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + IntegerBinding.intToEntry(newConfigVersion, value); + OperationStatus status = cursor.put(key, value); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Error setting config version: " + status); + } + } + cursor.close(); + cursor = null; + txn.commit(); + } + finally + { + closeCursorSafely(cursor); + } + + } + + private int getConfigVersion() throws AMQStoreException + { + Cursor cursor = null; + try + { + cursor = getConfigVersionDb().openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + return IntegerBinding.entryToInt(value); + } + + // Insert 0 as the default config version + IntegerBinding.intToEntry(0,value); + ByteBinding.byteToEntry((byte) 0,key); + OperationStatus status = getConfigVersionDb().put(null, key, value); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Error initialising config version: " + status); + } + return 0; + } + finally + { + closeCursorSafely(cursor); + } + } + + private void loadConfiguredObjects(ConfigurationRecoveryHandler crh) throws DatabaseException, AMQStoreException + { + Cursor cursor = null; + try + { + cursor = getConfiguredObjectsDb().openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + UUID id = UUIDTupleBinding.getInstance().entryToObject(key); + + ConfiguredObjectRecord configuredObject = new ConfiguredObjectBinding(id).entryToObject(value); + crh.configuredObject(configuredObject.getId(),configuredObject.getType(),configuredObject.getAttributes()); + } + + } + finally + { + closeCursorSafely(cursor); + } + } + + private void closeCursorSafely(Cursor cursor) throws AMQStoreException + { + if (cursor != null) + { + try { - throw de; + cursor.close(); + } + catch(DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Cannot close cursor", e); } } } + + private void recoverMessages(MessageStoreRecoveryHandler msrh) throws AMQStoreException + { + StoredMessageRecoveryHandler mrh = msrh.begin(); + + Cursor cursor = null; + try + { + cursor = getMessageMetaDataDb().openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + MessageMetaDataBinding valueBinding = MessageMetaDataBinding.getInstance(); + + long maxId = 0; + + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + long messageId = LongBinding.entryToLong(key); + StorableMessageMetaData metaData = valueBinding.entryToObject(value); + + StoredBDBMessage message = new StoredBDBMessage(messageId, metaData, true); + + mrh.message(message); + + maxId = Math.max(maxId, messageId); + } + + _messageId.set(maxId); + mrh.completeMessageRecovery(); + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Cannot recover messages", e); + } + finally + { + closeCursorSafely(cursor); + } + } + + private void recoverQueueEntries(TransactionLogRecoveryHandler recoveryHandler) + throws AMQStoreException + { + QueueEntryRecoveryHandler qerh = recoveryHandler.begin(this); + + ArrayList<QueueEntryKey> entries = new ArrayList<QueueEntryKey>(); + + Cursor cursor = null; + try + { + cursor = getDeliveryDb().openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); + + DatabaseEntry value = new DatabaseEntry(); + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + QueueEntryKey qek = keyBinding.entryToObject(key); + + entries.add(qek); + } + + try + { + cursor.close(); + } + finally + { + cursor = null; + } + + for(QueueEntryKey entry : entries) + { + UUID queueId = entry.getQueueId(); + long messageId = entry.getMessageId(); + qerh.queueEntry(queueId, messageId); + } + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Cannot recover queue entries", e); + } + finally + { + closeCursorSafely(cursor); + } + + TransactionLogRecoveryHandler.DtxRecordRecoveryHandler dtxrh = qerh.completeQueueEntryRecovery(); + + cursor = null; + try + { + cursor = getXidDb().openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + XidBinding keyBinding = XidBinding.getInstance(); + PreparedTransactionBinding valueBinding = new PreparedTransactionBinding(); + DatabaseEntry value = new DatabaseEntry(); + + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + Xid xid = keyBinding.entryToObject(key); + PreparedTransaction preparedTransaction = valueBinding.entryToObject(value); + dtxrh.dtxRecord(xid.getFormat(),xid.getGlobalId(),xid.getBranchId(), + preparedTransaction.getEnqueues(),preparedTransaction.getDequeues()); + } + + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Cannot recover transactions", e); + } + finally + { + closeCursorSafely(cursor); + } + + + dtxrh.completeDtxRecordRecovery(); + } + + public void removeMessage(long messageId, boolean sync) throws AMQStoreException + { + + boolean complete = false; + com.sleepycat.je.Transaction tx = null; + + Random rand = null; + int attempts = 0; + try + { + do + { + tx = null; + try + { + tx = _environmentFacade.getEnvironment().beginTransaction(null, null); + + //remove the message meta data from the store + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Removing message id " + messageId); + } + + + OperationStatus status = getMessageMetaDataDb().delete(tx, key); + if (status == OperationStatus.NOTFOUND) + { + LOGGER.info("Message not found (attempt to remove failed - probably application initiated rollback) " + + messageId); + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Deleted metadata for message " + messageId); + } + + //now remove the content data from the store if there is any. + DatabaseEntry contentKeyEntry = new DatabaseEntry(); + LongBinding.longToEntry(messageId, contentKeyEntry); + getMessageContentDb().delete(tx, contentKeyEntry); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Deleted content for message " + messageId); + } + + _environmentFacade.commit(tx); + _committer.commit(tx, sync); + + complete = true; + tx = null; + } + catch (LockConflictException e) + { + try + { + if(tx != null) + { + tx.abort(); + } + } + catch(DatabaseException e2) + { + LOGGER.warn("Unable to abort transaction after LockConflictExcption on removal of message with id " + messageId, e2); + // rethrow the original log conflict exception, the secondary exception should already have + // been logged. + throw _environmentFacade.handleDatabaseException("Cannot remove message with id " + messageId, e); + } + + + LOGGER.warn("Lock timeout exception. Retrying (attempt " + + (attempts+1) + " of "+ LOCK_RETRY_ATTEMPTS +") " + e); + + if(++attempts < LOCK_RETRY_ATTEMPTS) + { + if(rand == null) + { + rand = new Random(); + } + + try + { + Thread.sleep(500l + (long)(500l * rand.nextDouble())); + } + catch (InterruptedException e1) + { + + } + } + else + { + // rethrow the lock conflict exception since we could not solve by retrying + throw _environmentFacade.handleDatabaseException("Cannot remove messages", e); + } + } + } + while(!complete); + } + catch (DatabaseException e) + { + LOGGER.error("Unexpected BDB exception", e); + + try + { + abortTransactionIgnoringException("Error aborting transaction on removal of message with id " + messageId, tx); + } + finally + { + tx = null; + } + + throw _environmentFacade.handleDatabaseException("Error removing message with id " + messageId + " from database: " + e.getMessage(), e); + } + finally + { + try + { + abortTransactionIgnoringException("Error aborting transaction on removal of message with id " + messageId, tx); + } + finally + { + tx = null; + } + } + } + + private void abortTransactionIgnoringException(String errorMessage, com.sleepycat.je.Transaction tx) + { + try + { + if (tx != null) + { + tx.abort(); + } + } + catch (DatabaseException e1) + { + // We need the possible side effect of the handler restarting the environment but don't care about the exception + _environmentFacade.handleDatabaseException(null, e1); + LOGGER.warn(errorMessage, e1); + } + } + + @Override + public void create(UUID id, String type, Map<String, Object> attributes) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + ConfiguredObjectRecord configuredObject = new ConfiguredObjectRecord(id, type, attributes); + storeConfiguredObjectEntry(configuredObject); + } + } + @Override - protected void closeInternal() throws Exception + public void remove(UUID id, String type) throws AMQStoreException + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("public void remove(id = " + id + ", type="+type+"): called"); + } + OperationStatus status = removeConfiguredObject(null, id); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Configured object of type " + type + " with id " + id + " not found"); + } + } + + @Override + public UUID[] removeConfiguredObjects(final UUID... objects) throws AMQStoreException + { + com.sleepycat.je.Transaction txn = _environmentFacade.getEnvironment().beginTransaction(null, null); + Collection<UUID> removed = new ArrayList<UUID>(objects.length); + for(UUID id : objects) + { + if(removeConfiguredObject(txn, id) == OperationStatus.SUCCESS) + { + removed.add(id); + } + } + commitTransaction(txn); + return removed.toArray(new UUID[removed.size()]); + } + + private void commitTransaction(com.sleepycat.je.Transaction txn) throws AMQStoreException + { + try + { + txn.commit(); + } + catch(DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Cannot commit transaction on configured objects removal", e); + } + } + + @Override + public void update(UUID id, String type, Map<String, Object> attributes) throws AMQStoreException + { + update(false, id, type, attributes, null); + } + + @Override + public void update(ConfiguredObjectRecord... records) throws AMQStoreException + { + update(false, records); + } + + @Override + public void update(boolean createIfNecessary, ConfiguredObjectRecord... records) throws AMQStoreException + { + com.sleepycat.je.Transaction txn = _environmentFacade.getEnvironment().beginTransaction(null, null); + for(ConfiguredObjectRecord record : records) + { + update(createIfNecessary, record.getId(), record.getType(), record.getAttributes(), txn); + } + commitTransaction(txn); + } + + private void update(boolean createIfNecessary, UUID id, String type, Map<String, Object> attributes, com.sleepycat.je.Transaction txn) throws AMQStoreException + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Updating " +type + ", id: " + id); + } + + try + { + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding keyBinding = UUIDTupleBinding.getInstance(); + keyBinding.objectToEntry(id, key); + + DatabaseEntry value = new DatabaseEntry(); + DatabaseEntry newValue = new DatabaseEntry(); + ConfiguredObjectBinding configuredObjectBinding = ConfiguredObjectBinding.getInstance(); + + OperationStatus status = getConfiguredObjectsDb().get(txn, key, value, LockMode.DEFAULT); + if (status == OperationStatus.SUCCESS || (createIfNecessary && status == OperationStatus.NOTFOUND)) + { + ConfiguredObjectRecord newQueueRecord = new ConfiguredObjectRecord(id, type, attributes); + + // write the updated entry to the store + configuredObjectBinding.objectToEntry(newQueueRecord, newValue); + status = getConfiguredObjectsDb().put(txn, key, newValue); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Error updating configuration details within the store: " + status); + } + } + else if (status != OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Error finding configuration details within the store: " + status); + } + } + catch (DatabaseException e) + { + if (txn != null) + { + abortTransactionIgnoringException("Error updating configuration details within the store: " + e.getMessage(), txn); + } + throw _environmentFacade.handleDatabaseException("Error updating configuration details within the store: " + e,e); + } + } + + /** + * Places a message onto a specified queue, in a given transaction. + * + * @param tx The transaction for the operation. + * @param queue The the queue to place the message on. + * @param messageId The message to enqueue. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + public void enqueueMessage(final com.sleepycat.je.Transaction tx, final TransactionLogResource queue, + long messageId) throws AMQStoreException + { + + DatabaseEntry key = new DatabaseEntry(); + QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); + QueueEntryKey dd = new QueueEntryKey(queue.getId(), messageId); + keyBinding.objectToEntry(dd, key); + DatabaseEntry value = new DatabaseEntry(); + ByteBinding.byteToEntry((byte) 0, value); + + try + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Enqueuing message " + messageId + " on queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + queue.getId() + + " in transaction " + tx); + } + getDeliveryDb().put(tx, key, value); + } + catch (DatabaseException e) + { + LOGGER.error("Failed to enqueue: " + e.getMessage(), e); + throw _environmentFacade.handleDatabaseException("Error writing enqueued message with id " + messageId + " for queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + queue.getId() + + " to database", e); + } + } + + /** + * Extracts a message from a specified queue, in a given transaction. + * + * @param tx The transaction for the operation. + * @param queue The queue to take the message from. + * @param messageId The message to dequeue. + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + public void dequeueMessage(final com.sleepycat.je.Transaction tx, final TransactionLogResource queue, + long messageId) throws AMQStoreException + { + + DatabaseEntry key = new DatabaseEntry(); + QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); + QueueEntryKey queueEntryKey = new QueueEntryKey(queue.getId(), messageId); + UUID id = queue.getId(); + keyBinding.objectToEntry(queueEntryKey, key); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Dequeue message id " + messageId + " from queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id); + } + + try + { + + OperationStatus status = getDeliveryDb().delete(tx, key); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Unable to find message with id " + messageId + " on queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id); + } + else if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Unable to remove message with id " + messageId + " on queue" + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id); + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Removed message " + messageId + " on queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id + + " from delivery db"); + + } + } + catch (DatabaseException e) + { + + LOGGER.error("Failed to dequeue message " + messageId + " in transaction " + tx , e); + + throw _environmentFacade.handleDatabaseException("Error accessing database while dequeuing message: " + e.getMessage(), e); + } + } + + + private void recordXid(com.sleepycat.je.Transaction txn, + long format, + byte[] globalId, + byte[] branchId, + org.apache.qpid.server.store.Transaction.Record[] enqueues, + org.apache.qpid.server.store.Transaction.Record[] dequeues) throws AMQStoreException + { + DatabaseEntry key = new DatabaseEntry(); + Xid xid = new Xid(format, globalId, branchId); + XidBinding keyBinding = XidBinding.getInstance(); + keyBinding.objectToEntry(xid,key); + + DatabaseEntry value = new DatabaseEntry(); + PreparedTransaction preparedTransaction = new PreparedTransaction(enqueues, dequeues); + PreparedTransactionBinding valueBinding = new PreparedTransactionBinding(); + valueBinding.objectToEntry(preparedTransaction, value); + + try + { + getXidDb().put(txn, key, value); + } + catch (DatabaseException e) + { + LOGGER.error("Failed to write xid: " + e.getMessage(), e); + throw _environmentFacade.handleDatabaseException("Error writing xid to database", e); + } + } + + private void removeXid(com.sleepycat.je.Transaction txn, long format, byte[] globalId, byte[] branchId) + throws AMQStoreException + { + DatabaseEntry key = new DatabaseEntry(); + Xid xid = new Xid(format, globalId, branchId); + XidBinding keyBinding = XidBinding.getInstance(); + + keyBinding.objectToEntry(xid, key); + + + try + { + + OperationStatus status = getXidDb().delete(txn, key); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Unable to find xid"); + } + else if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Unable to remove xid"); + } + + } + catch (DatabaseException e) + { + + LOGGER.error("Failed to remove xid in transaction " + txn, e); + + throw _environmentFacade.handleDatabaseException("Error accessing database while removing xid: " + e.getMessage(), e); + } + } + + /** + * Commits all operations performed within a given transaction. + * + * @param tx The transaction to commit all operations for. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + private StoreFuture commitTranImpl(final com.sleepycat.je.Transaction tx, boolean syncCommit) throws AMQStoreException + { + if (tx == null) + { + throw new AMQStoreException("Fatal internal error: transactional is null at commitTran"); + } + + _environmentFacade.commit(tx); + StoreFuture result = _committer.commit(tx, syncCommit); + + if (LOGGER.isDebugEnabled()) + { + String transactionType = syncCommit ? "synchronous" : "asynchronous"; + LOGGER.debug("commitTranImpl completed " + transactionType + " transaction " + tx); + } + + return result; + } + + /** + * Abandons all operations performed within a given transaction. + * + * @param tx The transaction to abandon. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + public void abortTran(final com.sleepycat.je.Transaction tx) throws AMQStoreException + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("abortTran called for transaction " + tx); + } + + try + { + tx.abort(); + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Error aborting transaction: " + e.getMessage(), e); + } + } + + /** + * Primarily for testing purposes. + * + * @param queueId + * + * @return a list of message ids for messages enqueued for a particular queue + */ + List<Long> getEnqueuedMessages(UUID queueId) throws AMQStoreException + { + Cursor cursor = null; + try + { + cursor = getDeliveryDb().openCursor(null, null); + + DatabaseEntry key = new DatabaseEntry(); + + QueueEntryKey dd = new QueueEntryKey(queueId, 0); + + QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); + keyBinding.objectToEntry(dd, key); + + DatabaseEntry value = new DatabaseEntry(); + + LinkedList<Long> messageIds = new LinkedList<Long>(); + + OperationStatus status = cursor.getSearchKeyRange(key, value, LockMode.DEFAULT); + dd = keyBinding.entryToObject(key); + + while ((status == OperationStatus.SUCCESS) && dd.getQueueId().equals(queueId)) + { + + messageIds.add(dd.getMessageId()); + status = cursor.getNext(key, value, LockMode.DEFAULT); + if (status == OperationStatus.SUCCESS) + { + dd = keyBinding.entryToObject(key); + } + } + + return messageIds; + } + catch (DatabaseException e) + { + throw new AMQStoreException("Database error: " + e.getMessage(), e); + } + finally + { + closeCursorSafely(cursor); + } + } + + /** + * Return a valid, currently unused message id. + * + * @return A fresh message id. + */ + public long getNewMessageId() + { + return _messageId.incrementAndGet(); + } + + /** + * Stores a chunk of message data. + * + * @param tx The transaction for the operation. + * @param messageId The message to store the data for. + * @param offset The offset of the data chunk in the message. + * @param contentBody The content of the data chunk. + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + protected void addContent(final com.sleepycat.je.Transaction tx, long messageId, int offset, + ByteBuffer contentBody) throws AMQStoreException + { + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + DatabaseEntry value = new DatabaseEntry(); + ContentBinding messageBinding = ContentBinding.getInstance(); + messageBinding.objectToEntry(contentBody.array(), value); + try + { + OperationStatus status = getMessageContentDb().put(tx, key, value); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Error adding content for message id " + messageId + ": " + status); + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Storing content for message " + messageId + " in transaction " + tx); + + } + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Error writing AMQMessage with id " + messageId + " to database: " + e.getMessage(), e); + } + } + + /** + * Stores message meta-data. + * + * @param tx The transaction for the operation. + * @param messageId The message to store the data for. + * @param messageMetaData The message meta data to store. + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + private void storeMetaData(final com.sleepycat.je.Transaction tx, long messageId, + StorableMessageMetaData messageMetaData) + throws AMQStoreException { - _commitThreadWrapper.stopCommitThread(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("storeMetaData called for transaction " + tx + + ", messageId " + messageId + + ", messageMetaData " + messageMetaData); + } + + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + DatabaseEntry value = new DatabaseEntry(); + + MessageMetaDataBinding messageBinding = MessageMetaDataBinding.getInstance(); + messageBinding.objectToEntry(messageMetaData, value); + try + { + getMessageMetaDataDb().put(tx, key, value); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Storing message metadata for message id " + messageId + " in transaction " + tx); + } + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Error writing message metadata with id " + messageId + " to database: " + e.getMessage(), e); + } + } + + /** + * Retrieves message meta-data. + * + * @param messageId The message to get the meta-data for. + * + * @return The message meta data. + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + public StorableMessageMetaData getMessageMetaData(long messageId) throws AMQStoreException + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("public MessageMetaData getMessageMetaData(Long messageId = " + + messageId + "): called"); + } + + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + DatabaseEntry value = new DatabaseEntry(); + MessageMetaDataBinding messageBinding = MessageMetaDataBinding.getInstance(); + + try + { + OperationStatus status = getMessageMetaDataDb().get(null, key, value, LockMode.READ_UNCOMMITTED); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Metadata not found for message with id " + messageId); + } + + StorableMessageMetaData mdd = messageBinding.entryToObject(value); + + return mdd; + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Error reading message metadata for message with id " + messageId + ": " + e.getMessage(), e); + } + } + + /** + * Fills the provided ByteBuffer with as much content for the specified message as possible, starting + * from the specified offset in the message. + * + * @param messageId The message to get the data for. + * @param offset The offset of the data within the message. + * @param dst The destination of the content read back + * + * @return The number of bytes inserted into the destination + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + public int getContent(long messageId, int offset, ByteBuffer dst) throws AMQStoreException + { + DatabaseEntry contentKeyEntry = new DatabaseEntry(); + LongBinding.longToEntry(messageId, contentKeyEntry); + DatabaseEntry value = new DatabaseEntry(); + ContentBinding contentTupleBinding = ContentBinding.getInstance(); + + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Message Id: " + messageId + " Getting content body from offset: " + offset); + } + + try + { + + int written = 0; + OperationStatus status = getMessageContentDb().get(null, contentKeyEntry, value, LockMode.READ_UNCOMMITTED); + if (status == OperationStatus.SUCCESS) + { + byte[] dataAsBytes = contentTupleBinding.entryToObject(value); + int size = dataAsBytes.length; + if (offset > size) + { + throw new RuntimeException("Offset " + offset + " is greater than message size " + size + + " for message id " + messageId + "!"); + + } + + written = size - offset; + if(written > dst.remaining()) + { + written = dst.remaining(); + } + + dst.put(dataAsBytes, offset, written); + } + return written; + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Error getting AMQMessage with id " + messageId + " to database: " + e.getMessage(), e); + } + } - super.closeInternal(); + @Override + public boolean isPersistent() + { + return true; } @Override - protected StoreFuture commit(com.sleepycat.je.Transaction tx, boolean syncCommit) throws DatabaseException + @SuppressWarnings("unchecked") + public <T extends StorableMessageMetaData> StoredMessage<T> addMessage(T metaData) { + if(metaData.isPersistent()) + { + return (StoredMessage<T>) new StoredBDBMessage(getNewMessageId(), metaData); + } + else + { + return new StoredMemoryMessage(getNewMessageId(), metaData); + } + } + + /** + * Makes the specified configured object persistent. + * + * @param configuredObject Details of the configured object to store. + * @throws AMQStoreException If the operation fails for any reason. + */ + private void storeConfiguredObjectEntry(ConfiguredObjectRecord configuredObject) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + LOGGER.debug("Storing configured object: " + configuredObject); + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding keyBinding = UUIDTupleBinding.getInstance(); + keyBinding.objectToEntry(configuredObject.getId(), key); + + DatabaseEntry value = new DatabaseEntry(); + ConfiguredObjectBinding queueBinding = ConfiguredObjectBinding.getInstance(); + + queueBinding.objectToEntry(configuredObject, value); + try + { + OperationStatus status = getConfiguredObjectsDb().put(null, key, value); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Error writing configured object " + configuredObject + " to database: " + + status); + } + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Error writing configured object " + configuredObject + + " to database: " + e.getMessage(), e); + } + } + } + + private OperationStatus removeConfiguredObject(Transaction tx, UUID id) throws AMQStoreException + { + + LOGGER.debug("Removing configured object: " + id); + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding uuidBinding = UUIDTupleBinding.getInstance(); + uuidBinding.objectToEntry(id, key); try { - tx.commitNoSync(); + return getConfiguredObjectsDb().delete(tx, key); } - catch(DatabaseException de) + catch (DatabaseException e) { - LOGGER.error("Got DatabaseException on commit, closing environment", de); + throw _environmentFacade.handleDatabaseException("Error deleting of configured object with id " + id + " from database", e); + } + } + + + + private class StoredBDBMessage implements StoredMessage<StorableMessageMetaData> + { + + private final long _messageId; + private final boolean _isRecovered; + + private StorableMessageMetaData _metaData; + private volatile SoftReference<StorableMessageMetaData> _metaDataRef; - closeEnvironmentSafely(); + private byte[] _data; + private volatile SoftReference<byte[]> _dataRef; - throw de; + StoredBDBMessage(long messageId, StorableMessageMetaData metaData) + { + this(messageId, metaData, false); + } + + StoredBDBMessage(long messageId, StorableMessageMetaData metaData, boolean isRecovered) + { + _messageId = messageId; + _isRecovered = isRecovered; + + if(!_isRecovered) + { + _metaData = metaData; + } + _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); + } + + public StorableMessageMetaData getMetaData() + { + StorableMessageMetaData metaData = _metaDataRef.get(); + if(metaData == null) + { + try + { + metaData = BDBMessageStore.this.getMessageMetaData(_messageId); + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); + } + + return metaData; + } + + public long getMessageNumber() + { + return _messageId; + } + + public void addContent(int offsetInMessage, java.nio.ByteBuffer src) + { + src = src.slice(); + + if(_data == null) + { + _data = new byte[src.remaining()]; + _dataRef = new SoftReference<byte[]>(_data); + src.duplicate().get(_data); + } + else + { + byte[] oldData = _data; + _data = new byte[oldData.length + src.remaining()]; + _dataRef = new SoftReference<byte[]>(_data); + + System.arraycopy(oldData,0,_data,0,oldData.length); + src.duplicate().get(_data, oldData.length, src.remaining()); + } + + } + + public int getContent(int offsetInMessage, java.nio.ByteBuffer dst) + { + byte[] data = _dataRef == null ? null : _dataRef.get(); + if(data != null) + { + int length = Math.min(dst.remaining(), data.length - offsetInMessage); + dst.put(data, offsetInMessage, length); + return length; + } + else + { + try + { + return BDBMessageStore.this.getContent(_messageId, offsetInMessage, dst); + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + } + } + + public ByteBuffer getContent(int offsetInMessage, int size) + { + byte[] data = _dataRef == null ? null : _dataRef.get(); + if(data != null) + { + return ByteBuffer.wrap(data,offsetInMessage,size); + } + else + { + ByteBuffer buf = ByteBuffer.allocate(size); + int length = getContent(offsetInMessage, buf); + buf.limit(length); + buf.position(0); + return buf; + } } - return _commitThreadWrapper.commit(tx, syncCommit); + synchronized void store(com.sleepycat.je.Transaction txn) + { + if (!stored()) + { + try + { + _dataRef = new SoftReference<byte[]>(_data); + BDBMessageStore.this.storeMetaData(txn, _messageId, _metaData); + BDBMessageStore.this.addContent(txn, _messageId, 0, + _data == null ? ByteBuffer.allocate(0) : ByteBuffer.wrap(_data)); + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + catch (RuntimeException e) + { + LOGGER.error("RuntimeException during store", e); + throw e; + } + finally + { + _metaData = null; + _data = null; + } + } + } + + public synchronized StoreFuture flushToStore() + { + if(!stored()) + { + try + { + com.sleepycat.je.Transaction txn; + try + { + txn = _environmentFacade.getEnvironment().beginTransaction( + null, null); + } + catch (DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("failed to begin transaction", e); + } + store(txn); + _environmentFacade.commit(txn); + _committer.commit(txn, true); + + storedSizeChangeOccured(getMetaData().getContentSize()); + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + } + return StoreFuture.IMMEDIATE_FUTURE; + } + + public void remove() + { + try + { + int delta = getMetaData().getContentSize(); + BDBMessageStore.this.removeMessage(_messageId, false); + storedSizeChangeOccured(-delta); + + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + } + + private boolean stored() + { + return _metaData == null || _isRecovered; + } + } + + private class BDBTransaction implements org.apache.qpid.server.store.Transaction + { + private com.sleepycat.je.Transaction _txn; + private int _storeSizeIncrease; + + private BDBTransaction() throws AMQStoreException + { + try + { + _txn = _environmentFacade.getEnvironment().beginTransaction(null, null); + } + catch(DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Cannot create store transaction", e); + } + } + + public void enqueueMessage(TransactionLogResource queue, EnqueueableMessage message) throws AMQStoreException + { + if(message.getStoredMessage() instanceof StoredBDBMessage) + { + final StoredBDBMessage storedMessage = (StoredBDBMessage) message.getStoredMessage(); + storedMessage.store(_txn); + _storeSizeIncrease += storedMessage.getMetaData().getContentSize(); + } + + BDBMessageStore.this.enqueueMessage(_txn, queue, message.getMessageNumber()); + } + + public void dequeueMessage(TransactionLogResource queue, EnqueueableMessage message) throws AMQStoreException + { + BDBMessageStore.this.dequeueMessage(_txn, queue, message.getMessageNumber()); + } + + public void commitTran() throws AMQStoreException + { + BDBMessageStore.this.commitTranImpl(_txn, true); + BDBMessageStore.this.storedSizeChangeOccured(_storeSizeIncrease); + } + + public StoreFuture commitTranAsync() throws AMQStoreException + { + BDBMessageStore.this.storedSizeChangeOccured(_storeSizeIncrease); + return BDBMessageStore.this.commitTranImpl(_txn, false); + } + + public void abortTran() throws AMQStoreException + { + BDBMessageStore.this.abortTran(_txn); + } + + public void removeXid(long format, byte[] globalId, byte[] branchId) throws AMQStoreException + { + BDBMessageStore.this.removeXid(_txn, format, globalId, branchId); + } + + public void recordXid(long format, byte[] globalId, byte[] branchId, Record[] enqueues, + Record[] dequeues) throws AMQStoreException + { + BDBMessageStore.this.recordXid(_txn, format, globalId, branchId, enqueues, dequeues); + } + } + + private void storedSizeChangeOccured(final int delta) throws AMQStoreException + { + try + { + storedSizeChange(delta); + } + catch(DatabaseException e) + { + throw _environmentFacade.handleDatabaseException("Stored size change exception", e); + } + } + + private void storedSizeChange(final int delta) + { + if(getPersistentSizeHighThreshold() > 0) + { + synchronized (this) + { + // the delta supplied is an approximation of a store size change. we don;t want to check the statistic every + // time, so we do so only when there's been enough change that it is worth looking again. We do this by + // assuming the total size will change by less than twice the amount of the message data change. + long newSize = _totalStoreSize += 2*delta; + + if(!_limitBusted && newSize > getPersistentSizeHighThreshold()) + { + _totalStoreSize = getSizeOnDisk(); + + if(_totalStoreSize > getPersistentSizeHighThreshold()) + { + _limitBusted = true; + _eventManager.notifyEvent(Event.PERSISTENT_MESSAGE_SIZE_OVERFULL); + } + } + else if(_limitBusted && newSize < getPersistentSizeLowThreshold()) + { + long oldSize = _totalStoreSize; + _totalStoreSize = getSizeOnDisk(); + + if(oldSize <= _totalStoreSize) + { + + reduceSizeOnDisk(); + + _totalStoreSize = getSizeOnDisk(); + + } + + if(_totalStoreSize < getPersistentSizeLowThreshold()) + { + _limitBusted = false; + _eventManager.notifyEvent(Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); + } + + + } + } + } + } + + private void reduceSizeOnDisk() + { + _environmentFacade.getEnvironment().getConfig().setConfigParam(EnvironmentConfig.ENV_RUN_CLEANER, "false"); + boolean cleaned = false; + while (_environmentFacade.getEnvironment().cleanLog() > 0) + { + cleaned = true; + } + if (cleaned) + { + CheckpointConfig force = new CheckpointConfig(); + force.setForce(true); + _environmentFacade.getEnvironment().checkpoint(force); + } + + + _environmentFacade.getEnvironment().getConfig().setConfigParam(EnvironmentConfig.ENV_RUN_CLEANER, "true"); + } + + private long getSizeOnDisk() + { + return _environmentFacade.getEnvironment().getStats(null).getTotalLogSize(); + } + + private long getPersistentSizeLowThreshold() + { + return _persistentSizeLowThreshold; + } + + private long getPersistentSizeHighThreshold() + { + return _persistentSizeHighThreshold; + } + + + @Override + public void onDelete() + { + String storeLocation = getStoreLocation(); + + if (storeLocation != null) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Deleting store " + storeLocation); + } + + File location = new File(storeLocation); + if (location.exists()) + { + if (!FileUtils.delete(location, true)) + { + LOGGER.error("Cannot delete " + storeLocation); + } + } + } } @Override public String getStoreType() { - return TYPE; + return _type; + } + + private Database getMessageContentDb() + { + return _environmentFacade.getOpenDatabase(MESSAGE_CONTENT_DB_NAME); + } + + private Database getConfiguredObjectsDb() + { + return _environmentFacade.getOpenDatabase(CONFIGURED_OBJECTS_DB_NAME); + } + + private Database getConfigVersionDb() + { + return _environmentFacade.getOpenDatabase(CONFIG_VERSION_DB_NAME); + } + + private Database getMessageMetaDataDb() + { + return _environmentFacade.getOpenDatabase(MESSAGE_META_DATA_DB_NAME); + } + + private Database getDeliveryDb() + { + return _environmentFacade.getOpenDatabase(DELIVERY_DB_NAME); + } + + private Database getXidDb() + { + return _environmentFacade.getOpenDatabase(XID_DB_NAME); } } diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreFactory.java index d7c8b23d39..4abe81c56c 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreFactory.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreFactory.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; + import org.apache.commons.configuration.Configuration; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.plugin.DurableConfigurationStoreFactory; @@ -37,7 +38,7 @@ public class BDBMessageStoreFactory implements MessageStoreFactory, DurableConfi @Override public String getType() { - return BDBMessageStore.TYPE; + return StandardEnvironmentFacade.TYPE; } @Override @@ -71,7 +72,7 @@ public class BDBMessageStoreFactory implements MessageStoreFactory, DurableConfi if(initialSize != 0) { - return Collections.singletonMap("bdbEnvironmentConfig", (Object)attributes); + return Collections.singletonMap(BDBMessageStore.ENVIRONMENT_CONFIGURATION, (Object)attributes); } else { diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/CommitThreadWrapper.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/CoalescingCommiter.java index 598d20146c..d36f9539dd 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/CommitThreadWrapper.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/CoalescingCommiter.java @@ -27,31 +27,40 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import org.apache.qpid.server.store.StoreFuture; -import com.sleepycat.je.CheckpointConfig; import com.sleepycat.je.DatabaseException; -import com.sleepycat.je.Environment; import com.sleepycat.je.Transaction; -public class CommitThreadWrapper +public class CoalescingCommiter implements Committer { private final CommitThread _commitThread; - - public CommitThreadWrapper(String name, Environment env) + + public CoalescingCommiter(String name, EnvironmentFacade environmentFacade) { - _commitThread = new CommitThread(name, env); + _commitThread = new CommitThread("Commit-Thread-" + name, environmentFacade); } - public void startCommitThread() + @Override + public void start() { _commitThread.start(); } - public void stopCommitThread() throws InterruptedException + @Override + public void stop() { _commitThread.close(); - _commitThread.join(); + try + { + _commitThread.join(); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + throw new RuntimeException("Commit thread has not shutdown", ie); + } } + @Override public StoreFuture commit(Transaction tx, boolean syncCommit) { BDBCommitFuture commitFuture = new BDBCommitFuture(_commitThread, tx, syncCommit); @@ -65,9 +74,9 @@ public class CommitThreadWrapper private final CommitThread _commitThread; private final Transaction _tx; - private DatabaseException _databaseException; + private final boolean _syncCommit; + private RuntimeException _databaseException; private boolean _complete; - private boolean _syncCommit; public BDBCommitFuture(CommitThread commitThread, Transaction tx, boolean syncCommit) { @@ -87,7 +96,7 @@ public class CommitThreadWrapper notifyAll(); } - public synchronized void abort(DatabaseException databaseException) + public synchronized void abort(RuntimeException databaseException) { _complete = true; _databaseException = databaseException; @@ -165,15 +174,13 @@ public class CommitThreadWrapper private final AtomicBoolean _stopped = new AtomicBoolean(false); private final Queue<BDBCommitFuture> _jobQueue = new ConcurrentLinkedQueue<BDBCommitFuture>(); - private final CheckpointConfig _config = new CheckpointConfig(); private final Object _lock = new Object(); - private Environment _environment; + private final EnvironmentFacade _environmentFacade; - public CommitThread(String name, Environment env) + public CommitThread(String name, EnvironmentFacade environmentFacade) { super(name); - _config.setForce(true); - _environment = env; + _environmentFacade = environmentFacade; } public void explicitNotify() @@ -194,7 +201,7 @@ public class CommitThreadWrapper { try { - // RHM-7 Periodically wake up and check, just in case we + // Periodically wake up and check, just in case we // missed a notification. Don't want to lock the broker hard. _lock.wait(1000); } @@ -219,7 +226,7 @@ public class CommitThreadWrapper startTime = System.currentTimeMillis(); } - _environment.flushLog(true); + _environmentFacade.getEnvironment().flushLog(true); if(LOGGER.isDebugEnabled()) { @@ -252,7 +259,7 @@ public class CommitThreadWrapper try { - _environment.close(); + _environmentFacade.close(); } catch (DatabaseException ex) { @@ -269,7 +276,10 @@ public class CommitThreadWrapper public void addJob(BDBCommitFuture commit, final boolean sync) { - + if (_stopped.get()) + { + throw new IllegalStateException("Commit thread is stopped"); + } _jobQueue.add(commit); if(sync) { @@ -282,9 +292,15 @@ public class CommitThreadWrapper public void close() { + RuntimeException e = new RuntimeException("Commit thread has been closed, transaction aborted"); synchronized (_lock) { _stopped.set(true); + BDBCommitFuture commit = null; + while ((commit = _jobQueue.poll()) != null) + { + commit.abort(e); + } _lock.notifyAll(); } } diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/Committer.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/Committer.java new file mode 100644 index 0000000000..36ee2ad306 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/Committer.java @@ -0,0 +1,55 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb; + +import org.apache.qpid.server.store.StoreFuture; + +import com.sleepycat.je.Transaction; + +public interface Committer +{ + void start(); + + StoreFuture commit(Transaction tx, boolean syncCommit); + + void stop(); + + Committer IMMEDIATE_FUTURE_COMMITTER = new Committer() + { + + @Override + public void start() + { + } + + @Override + public StoreFuture commit(Transaction tx, boolean syncCommit) + { + return StoreFuture.IMMEDIATE_FUTURE; + } + + @Override + public void stop() + { + } + }; + +}
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/EnvironmentFacade.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/EnvironmentFacade.java new file mode 100644 index 0000000000..60ff529203 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/EnvironmentFacade.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.AMQStoreException; + +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Environment; +import com.sleepycat.je.EnvironmentConfig; + +public interface EnvironmentFacade +{ + @SuppressWarnings("serial") + final Map<String, String> ENVCONFIG_DEFAULTS = Collections.unmodifiableMap(new HashMap<String, String>() + {{ + put(EnvironmentConfig.LOCK_N_LOCK_TABLES, "7"); + // Turn off stats generation - feature introduced (and on by default) from BDB JE 5.0.84 + put(EnvironmentConfig.STATS_COLLECT, "false"); + }}); + + Environment getEnvironment(); + + Committer createCommitter(String name); + + void openDatabases(DatabaseConfig dbConfig, String... databaseNames); + + Database getOpenDatabase(String name); + + void commit(com.sleepycat.je.Transaction tx) throws AMQStoreException; + + AMQStoreException handleDatabaseException(String contextMessage, DatabaseException e); + + void close(); + + String getStoreLocation(); + +} diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/HAMessageStoreSmokeTest.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/EnvironmentFacadeFactory.java index 3f32df4b0c..b784e436b9 100644 --- a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/HAMessageStoreSmokeTest.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/EnvironmentFacadeFactory.java @@ -1,4 +1,5 @@ /* + * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -19,26 +20,13 @@ */ package org.apache.qpid.server.store.berkeleydb; -import org.apache.commons.configuration.ConfigurationException; import org.apache.qpid.server.model.VirtualHost; -import org.apache.qpid.test.utils.QpidTestCase; - -import static org.mockito.Mockito.mock; -public class HAMessageStoreSmokeTest extends QpidTestCase +public interface EnvironmentFacadeFactory { - private final BDBHAMessageStore _store = new BDBHAMessageStore(); - public void testMissingHAConfigThrowsException() throws Exception - { - try - { - _store.configure(mock(VirtualHost.class)); - fail("Expected an exception to be thrown"); - } - catch (ConfigurationException ce) - { - assertTrue(ce.getMessage().contains("BDB HA configuration key not found")); - } - } + EnvironmentFacade createEnvironmentFacade(VirtualHost virtualHost, boolean isMessageStore); + + String getType(); + } diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/LoggingAsyncExceptionListener.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/LoggingAsyncExceptionListener.java new file mode 100644 index 0000000000..b13766a136 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/LoggingAsyncExceptionListener.java @@ -0,0 +1,37 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb; + +import org.apache.log4j.Logger; + +import com.sleepycat.je.ExceptionEvent; +import com.sleepycat.je.ExceptionListener; + +public class LoggingAsyncExceptionListener implements ExceptionListener +{ + private static final Logger LOGGER = Logger.getLogger(LoggingAsyncExceptionListener.class); + + @Override + public void exceptionThrown(ExceptionEvent event) + { + LOGGER.error("Asynchronous exception thrown by BDB thread '" + event.getThreadName() + "'", event.getException()); + } +}
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/StandardEnvironmentFacade.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/StandardEnvironmentFacade.java new file mode 100644 index 0000000000..3b6eef832b --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/StandardEnvironmentFacade.java @@ -0,0 +1,229 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; + +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Environment; +import com.sleepycat.je.EnvironmentConfig; + +public class StandardEnvironmentFacade implements EnvironmentFacade +{ + private static final Logger LOGGER = Logger.getLogger(StandardEnvironmentFacade.class); + public static final String TYPE = "BDB"; + + private final String _storePath; + private final Map<String, Database> _databases = new HashMap<String, Database>(); + + private Environment _environment; + + public StandardEnvironmentFacade(String storePath, Map<String, String> attributes) + { + _storePath = storePath; + + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Creating environment at environment path " + _storePath); + } + + File environmentPath = new File(storePath); + if (!environmentPath.exists()) + { + if (!environmentPath.mkdirs()) + { + throw new IllegalArgumentException("Environment path " + environmentPath + " could not be read or created. " + + "Ensure the path is correct and that the permissions are correct."); + } + } + + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setAllowCreate(true); + envConfig.setTransactional(true); + + for (Map.Entry<String, String> configItem : attributes.entrySet()) + { + LOGGER.debug("Setting EnvironmentConfig key " + configItem.getKey() + " to '" + configItem.getValue() + "'"); + envConfig.setConfigParam(configItem.getKey(), configItem.getValue()); + } + + envConfig.setExceptionListener(new LoggingAsyncExceptionListener()); + + _environment = new Environment(environmentPath, envConfig); + } + + @Override + public void commit(com.sleepycat.je.Transaction tx) throws AMQStoreException + { + try + { + tx.commitNoSync(); + } + catch (DatabaseException de) + { + LOGGER.error("Got DatabaseException on commit, closing environment", de); + + closeEnvironmentSafely(); + + throw handleDatabaseException("Got DatabaseException on commit", de); + } + } + + @Override + public void close() + { + closeDatabases(); + closeEnvironment(); + } + + private void closeDatabases() + { + RuntimeException firstThrownException = null; + for (Database database : _databases.values()) + { + try + { + database.close(); + } + catch(RuntimeException e) + { + if (firstThrownException == null) + { + firstThrownException = e; + } + } + } + if (firstThrownException != null) + { + throw firstThrownException; + } + } + + private void closeEnvironmentSafely() + { + if (_environment != null) + { + if (_environment.isValid()) + { + try + { + closeDatabases(); + } + catch(Exception e) + { + LOGGER.error("Exception closing environment databases", e); + } + } + try + { + _environment.close(); + } + catch (DatabaseException ex) + { + LOGGER.error("Exception closing store environment", ex); + } + catch (IllegalStateException ex) + { + LOGGER.error("Exception closing store environment", ex); + } + finally + { + _environment = null; + } + } + } + + @Override + public Environment getEnvironment() + { + return _environment; + } + + private void closeEnvironment() + { + if (_environment != null) + { + // Clean the log before closing. This makes sure it doesn't contain + // redundant data. Closing without doing this means the cleaner may + // not get a chance to finish. + try + { + _environment.cleanLog(); + } + finally + { + _environment.close(); + _environment = null; + } + } + } + + @Override + public AMQStoreException handleDatabaseException(String contextMessage, DatabaseException e) + { + if (_environment != null && !_environment.isValid()) + { + closeEnvironmentSafely(); + } + return new AMQStoreException(contextMessage, e); + } + + @Override + public void openDatabases(DatabaseConfig dbConfig, String... databaseNames) + { + for (String databaseName : databaseNames) + { + Database database = _environment.openDatabase(null, databaseName, dbConfig); + _databases .put(databaseName, database); + } + } + + @Override + public Database getOpenDatabase(String name) + { + Database database = _databases.get(name); + if (database == null) + { + throw new IllegalArgumentException("Database with name '" + name + "' has not been opened"); + } + return database; + } + + @Override + public Committer createCommitter(String name) + { + return new CoalescingCommiter(name, this); + } + + @Override + public String getStoreLocation() + { + return _storePath; + } + +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/StandardEnvironmentFacadeFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/StandardEnvironmentFacadeFactory.java new file mode 100644 index 0000000000..384ceba98a --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/StandardEnvironmentFacadeFactory.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.model.VirtualHost; + +public class StandardEnvironmentFacadeFactory implements EnvironmentFacadeFactory +{ + + @SuppressWarnings("unchecked") + @Override + public EnvironmentFacade createEnvironmentFacade(VirtualHost virtualHost, boolean isMessageStore) + { + Map<String, String> envConfigMap = new HashMap<String, String>(); + envConfigMap.putAll(EnvironmentFacade.ENVCONFIG_DEFAULTS); + + Object environmentConfigurationAttributes = virtualHost.getAttribute(BDBMessageStore.ENVIRONMENT_CONFIGURATION); + if (environmentConfigurationAttributes instanceof Map) + { + envConfigMap.putAll((Map<String, String>) environmentConfigurationAttributes); + } + + String name = virtualHost.getName(); + final String defaultPath = System.getProperty(BrokerProperties.PROPERTY_QPID_WORK) + File.separator + "bdbstore" + File.separator + name; + + String storeLocation; + if(isMessageStore) + { + storeLocation = (String) virtualHost.getAttribute(VirtualHost.STORE_PATH); + if(storeLocation == null) + { + storeLocation = defaultPath; + } + } + else // we are acting only as the durable config store + { + storeLocation = (String) virtualHost.getAttribute(VirtualHost.CONFIG_STORE_PATH); + if(storeLocation == null) + { + storeLocation = defaultPath; + } + } + + return new StandardEnvironmentFacade(storeLocation, envConfigMap); + } + + @Override + public String getType() + { + return StandardEnvironmentFacade.TYPE; + } + +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/DatabasePinger.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/DatabasePinger.java new file mode 100644 index 0000000000..38fdf34196 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/DatabasePinger.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import org.apache.qpid.server.store.berkeleydb.EnvironmentFacade; + +import com.sleepycat.bind.tuple.IntegerBinding; +import com.sleepycat.bind.tuple.LongBinding; +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Transaction; + +public class DatabasePinger +{ + public static final String PING_DATABASE_NAME = "PINGDB"; + private static final int ID = 0; + + public void pingDb(EnvironmentFacade facade) + { + try + { + final Database db = facade.getOpenDatabase(PING_DATABASE_NAME); + + DatabaseEntry key = new DatabaseEntry(); + IntegerBinding.intToEntry(ID, key); + + DatabaseEntry value = new DatabaseEntry(); + LongBinding.longToEntry(System.currentTimeMillis(), value); + Transaction txn = null; + try + { + txn = facade.getEnvironment().beginTransaction(null, null); + db.put(txn, key, value); + txn.commit(); + txn = null; + } + finally + { + try + { + if (txn != null) + { + txn.abort(); + } + } + finally + { + db.close(); + } + } + } + catch (DatabaseException de) + { + facade.handleDatabaseException("DatabaseException from DatabasePinger ", de); + } + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNode.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNode.java new file mode 100644 index 0000000000..ef2c48463b --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNode.java @@ -0,0 +1,437 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import java.lang.reflect.Type; +import java.security.AccessControlException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.AbstractAdapter; +import org.apache.qpid.server.model.adapter.NoStatistics; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.server.util.ParameterizedTypeImpl; + +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Durability; +import com.sleepycat.je.Durability.ReplicaAckPolicy; +import com.sleepycat.je.Durability.SyncPolicy; +import com.sleepycat.je.rep.ReplicatedEnvironment; + +public class LocalReplicationNode extends AbstractAdapter implements ReplicationNode +{ + + private static final Durability DEFAULT_DURABILITY = new Durability(SyncPolicy.NO_SYNC, SyncPolicy.NO_SYNC, + ReplicaAckPolicy.SIMPLE_MAJORITY); + static final boolean DEFAULT_DESIGNATED_PRIMARY = false; + static final int DEFAULT_PRIORITY = 1; + static final int DEFAULT_QUORUM_OVERRIDE = 0; + + @SuppressWarnings("serial") + static final Map<String, Object> DEFAULTS = new HashMap<String, Object>() + {{ + put(DURABILITY, DEFAULT_DURABILITY.toString()); + put(COALESCING_SYNC, true); + put(DESIGNATED_PRIMARY, DEFAULT_DESIGNATED_PRIMARY); + put(PRIORITY, DEFAULT_PRIORITY); + put(QUORUM_OVERRIDE, DEFAULT_QUORUM_OVERRIDE); + //TODO: add defaults for parameters and replicatedParameters + }}; + + @SuppressWarnings("serial") + private static final Map<String, Type> ATTRIBUTE_TYPES = new HashMap<String, Type>() + {{ + put(ID, UUID.class); + put(NAME, String.class); + put(GROUP_NAME, String.class); + put(HOST_PORT, String.class); + put(HELPER_HOST_PORT, String.class); + put(DURABILITY, String.class); + put(COALESCING_SYNC, Boolean.class); + put(DESIGNATED_PRIMARY, Boolean.class); + put(PRIORITY, Integer.class); + put(QUORUM_OVERRIDE, Integer.class); + put(ROLE, String.class); + put(JOIN_TIME, Long.class); + put(PARAMETERS, new ParameterizedTypeImpl(Map.class, String.class, String.class)); + put(REPLICATION_PARAMETERS, new ParameterizedTypeImpl(Map.class, String.class, String.class)); + put(STORE_PATH, String.class); + put(LAST_KNOWN_REPLICATION_TRANSACTION_ID, Long.class); + }}; + + static final String[] IMMUTABLE_ATTRIBUTES = {ReplicationNode.GROUP_NAME, ReplicationNode.HELPER_HOST_PORT, + ReplicationNode.HOST_PORT, ReplicationNode.COALESCING_SYNC, ReplicationNode.DURABILITY, + ReplicationNode.JOIN_TIME, ReplicationNode.LAST_KNOWN_REPLICATION_TRANSACTION_ID, ReplicationNode.NAME, + ReplicationNode.STORE_PATH, ReplicationNode.PARAMETERS, ReplicationNode.REPLICATION_PARAMETERS}; + + private final VirtualHost _virtualHost; + private volatile ReplicatedEnvironmentFacade _replicatedEnvironmentFacade; + + //TODO: add state management + public LocalReplicationNode(UUID id, Map<String, Object> attributes, VirtualHost virtualHost, TaskExecutor taskExecutor) + { + super(id, DEFAULTS, validateAttributes(MapValueConverter.convert(attributes, ATTRIBUTE_TYPES)), taskExecutor); + _virtualHost = virtualHost; + addParent(VirtualHost.class, virtualHost); + validateAttributes(attributes); + } + + private static Map<String, Object> validateAttributes(Map<String, Object> attributes) + { + if (attributes.get(NAME) == null) + { + throw new IllegalConfigurationException("Name is not specified"); + } + if (attributes.get(GROUP_NAME) == null) + { + throw new IllegalConfigurationException("Group name is not specified"); + } + if (attributes.get(HOST_PORT) == null) + { + throw new IllegalConfigurationException("Host and port attribute is not specified"); + } + if (attributes.get(HELPER_HOST_PORT) == null) + { + throw new IllegalConfigurationException("Helper host and port attribute is not specified"); + } + Object storePath = attributes.get(STORE_PATH); + if (storePath == null || storePath.equals("")) + { + throw new IllegalConfigurationException("Store path is not specified for the replication node"); + } + return attributes; + } + + @Override + public String getName() + { + return (String)getAttribute(NAME); + } + + @Override + public String setName(String currentName, String desiredName) + throws IllegalStateException, AccessControlException + { + throw new UnsupportedOperationException(); + } + + @Override + public State getActualState() + { + // TODO + return null; + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, + LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public Collection<String> getAttributeNames() + { + return ReplicationNode.AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(String attributeName) + { + if (ReplicationNode.ID.equals(attributeName)) + { + return getId(); + } + else if (ReplicationNode.LIFETIME_POLICY.equals(attributeName)) + { + return getLifetimePolicy(); + } + else if (ReplicationNode.DURABLE.equals(attributeName)) + { + return isDurable(); + } + else if(STATE.equals(attributeName)) + { + return getActualState(); + } + else if(TIME_TO_LIVE.equals(attributeName)) + { + return getLifetimePolicy(); + } + if (_replicatedEnvironmentFacade != null) + { + try + { + if(ROLE.equals(attributeName)) + { + return _replicatedEnvironmentFacade.getNodeState(); + } + else if(JOIN_TIME.equals(attributeName)) + { + return _replicatedEnvironmentFacade.getJoinTime(); + } + else if(LAST_KNOWN_REPLICATION_TRANSACTION_ID.equals(attributeName)) + { + return _replicatedEnvironmentFacade.getLastKnownReplicationTransactionId(); + } + else if(QUORUM_OVERRIDE.equals(attributeName)) + { + return _replicatedEnvironmentFacade.getElectableGroupSizeOverride(); + } + else if(DESIGNATED_PRIMARY.equals(attributeName)) + { + return _replicatedEnvironmentFacade.isDesignatedPrimary(); + } + else if(PRIORITY.equals(attributeName)) + { + return _replicatedEnvironmentFacade.getPriority(); + } + } + catch(IllegalStateException e) + { + // ignore, as attribute value will be returned from actual/default attribute maps if present + } + catch(DatabaseException e) + { + // ignore, as attribute value will be returned from actual/default attribute maps if present + } + } + return super.getAttribute(attributeName); + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public <C extends ConfiguredObject> Collection<C> getChildren(Class<C> clazz) + { + throw new UnsupportedOperationException(); + } + + @Override + public <C extends ConfiguredObject> C createChild(Class<C> childClass, + Map<String, Object> attributes, ConfiguredObject... otherParents) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean changeAttribute(final String name, final Object expected, final Object desired) + { + updateReplicatedEnvironmentFacade(name, desired); + if (!ROLE.equals(name)) + { + return super.changeAttribute(name, expected, desired); + } + return false; + } + + @Override + public void changeAttributes(Map<String, Object> attributes) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + Map<String, Object> convertedAttributes = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + + checkWhetherImmutableAttributeChanged(convertedAttributes); + + super.changeAttributes(convertedAttributes); + } + + private void updateReplicatedEnvironmentFacade(String attributeName, Object attributeValue) + { + if (_replicatedEnvironmentFacade != null) + { + if (PRIORITY.equals(attributeName)) + { + int priority = (Integer)attributeValue; + try + { + _replicatedEnvironmentFacade.setPriority(priority); + } + catch(Exception e) + { + throw new IllegalConfigurationException("Cannot set attribute " + PRIORITY + " to " + priority, e); + } + } + + if (DESIGNATED_PRIMARY.equals(attributeName)) + { + boolean designatedPrimary = (Boolean)attributeValue; + try + { + _replicatedEnvironmentFacade.setDesignatedPrimary(designatedPrimary); + } + catch(Exception e) + { + throw new IllegalConfigurationException("Cannot set attribute '" + DESIGNATED_PRIMARY + "' to " + designatedPrimary, e); + } + } + + if (QUORUM_OVERRIDE.equals(attributeName)) + { + int quorumOverride = (Integer)attributeValue; + try + { + _replicatedEnvironmentFacade.setElectableGroupSizeOverride(quorumOverride); + } + catch(Exception e) + { + throw new IllegalConfigurationException("Cannot set attribute '" + QUORUM_OVERRIDE + "' to " + quorumOverride, e); + } + } + } + + if (ROLE.equals(attributeName)) + { + String currentRole = (String)getAttribute(ROLE); + if (!ReplicatedEnvironment.State.REPLICA.name().equals(currentRole)) + { + throw new IllegalConfigurationException("Cannot transfer mastership when not a replica"); + } + + // we do not want to write role into the store + String role = (String)attributeValue; + + if (ReplicatedEnvironment.State.MASTER.name().equals(role) ) + { + try + { + _replicatedEnvironmentFacade.transferMasterToSelfAsynchronously(); + } + catch(Exception e) + { + throw new IllegalConfigurationException("Cannot transfer mastership", e); + } + } + else + { + throw new IllegalConfigurationException("Changing role to other value then " + ReplicatedEnvironment.State.MASTER.name() + " is unsupported"); + } + } + } + + private void checkWhetherImmutableAttributeChanged(Map<String, Object> convertedAttributes) + { + for (int i = 0; i < IMMUTABLE_ATTRIBUTES.length; i++) + { + String attributeName = IMMUTABLE_ATTRIBUTES[i]; + if (convertedAttributes.containsKey(attributeName)) + { + Object newValue = convertedAttributes.get(attributeName); + Object currentValue = getAttribute(attributeName); + if (currentValue == null) + { + if (newValue != null) + { + throw new IllegalConfigurationException("Cannot change value of immutable attribute " + attributeName); + } + } + else + { + if (!currentValue.equals(newValue)) + { + throw new IllegalConfigurationException("Cannot change value of immutable attribute " + attributeName); + } + } + } + } + } + + protected VirtualHost getVirtualHost() + { + return _virtualHost; + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + if (desiredState == State.ACTIVE || desiredState == State.STOPPED) + { + return true; + } + return false; + } + + @Override + public boolean isLocal() + { + return true; + } + + public void setReplicatedEnvironmentFacade(ReplicatedEnvironmentFacade replicatedEnvironmentFacade) + { + _replicatedEnvironmentFacade = replicatedEnvironmentFacade; + } + + public Object getActualAttribute(String attributeName) + { + return super.getAttribute(attributeName); + } + +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNodeFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNodeFactory.java new file mode 100644 index 0000000000..6c87b58299 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNodeFactory.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.plugin.ReplicationNodeFactory; +import org.apache.qpid.server.store.berkeleydb.BDBHAVirtualHostFactory; + +public class LocalReplicationNodeFactory implements ReplicationNodeFactory +{ + + @Override + public String getType() + { + return BDBHAVirtualHostFactory.TYPE; + } + + @Override + public ReplicationNode createInstance(UUID id, + Map<String, Object> attributes, VirtualHost virtualHost) + { + // TODO KW Temporary code + Broker broker = virtualHost.getParent(Broker.class); + if (broker == null) + { + throw new IllegalStateException("Cannot find the broker among virtual host parents"); + } + return new LocalReplicationNode(id, attributes, virtualHost, broker.getTaskExecutor()); + } + +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/RemoteReplicationNode.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/RemoteReplicationNode.java new file mode 100644 index 0000000000..de301b91ba --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/RemoteReplicationNode.java @@ -0,0 +1,365 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.security.AccessControlException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.AbstractAdapter; +import org.apache.qpid.server.model.adapter.NoStatistics; +import org.apache.qpid.server.util.MapValueConverter; + +import com.sleepycat.je.rep.MasterStateException; +import com.sleepycat.je.rep.NodeState; +import com.sleepycat.je.rep.ReplicatedEnvironment; +import com.sleepycat.je.rep.utilint.ServiceDispatcher.ServiceConnectFailedException; + +/** + * Represents a remote replication node in a BDB group. + */ +public class RemoteReplicationNode extends AbstractAdapter implements ReplicationNode +{ + private static final Logger LOGGER = Logger.getLogger(RemoteReplicationNode.class); + + @SuppressWarnings("serial") + private static final Map<String, Type> ATTRIBUTE_TYPES = new HashMap<String, Type>() + {{ + put(ROLE, String.class); + }}; + + private final com.sleepycat.je.rep.ReplicationNode _replicationNode; + private final String _hostPort; + private final String _groupName; + private final ReplicatedEnvironmentFacade _replicatedEnvironmentFacade; + + private volatile String _role; + private volatile long _joinTime; + private volatile long _lastTransactionId; + + private final AtomicReference<State> _state; + + public RemoteReplicationNode(com.sleepycat.je.rep.ReplicationNode replicationNode, VirtualHost virtualHost, + TaskExecutor taskExecutor, ReplicatedEnvironmentFacade replicatedEnvironmentFacade) + { + super(UUIDGenerator.generateReplicationNodeId(replicatedEnvironmentFacade.getGroupName(), replicationNode.getName()), null, null, taskExecutor); + addParent(VirtualHost.class, virtualHost); + _groupName = replicatedEnvironmentFacade.getGroupName(); + _hostPort = replicationNode.getHostName() + ":" + replicationNode.getPort(); + _replicationNode = replicationNode; + _replicatedEnvironmentFacade = replicatedEnvironmentFacade; + _state = new AtomicReference<State>(State.ACTIVE); + } + + @Override + public boolean isLocal() + { + return false; + } + + @Override + public String getName() + { + return (String)getAttribute(NAME); + } + + @Override + public String setName(String currentName, String desiredName) throws IllegalStateException, AccessControlException + { + throw new UnsupportedOperationException(); + } + + @Override + public State getActualState() + { + return _state.get(); + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public <C extends ConfiguredObject> Collection<C> getChildren(Class<C> clazz) + { + return Collections.emptySet(); + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + //TODO: Need to decide how to display STOPPED state on a remote node when a corresponding local node is in STOPPED state + if (desiredState == State.DELETED) + { + String nodeName = getName(); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Deleting node '" + nodeName + "' from group '" + _groupName + "'"); + } + + try + { + _replicatedEnvironmentFacade.removeNodeFromGroup(nodeName); + _state.set(State.DELETED); + return true; + } + catch(MasterStateException e) + { + throw new IllegalStateTransitionException("Node '" + nodeName + "' cannot be deleted when role is a master"); + } + catch (Exception e) + { + throw new IllegalStateTransitionException("Unexpected exception on node '" + nodeName + "' deletion", e); + } + } + return false; + } + + @Override + public Object getAttribute(String name) + { + if (ReplicationNode.ID.equals(name)) + { + return getId(); + } + else if (LIFETIME_POLICY.equals(name)) + { + return getLifetimePolicy(); + } + else if (DURABLE.equals(name)) + { + return isDurable(); + } + else if(STATE.equals(name)) + { + return getActualState(); + } + else if(TIME_TO_LIVE.equals(name)) + { + return getLifetimePolicy(); + } + else if (ROLE.equals(name)) + { + return _role; + } + else if (JOIN_TIME.equals(name)) + { + return _joinTime; + } + else if (LAST_KNOWN_REPLICATION_TRANSACTION_ID.equals(name)) + { + return _lastTransactionId; + } + else if (NAME.equals(name)) + { + return _replicationNode.getName(); + } + else if (GROUP_NAME.equals(name)) + { + return _groupName; + } + else if (HOST_PORT.equals(name)) + { + return _hostPort; + } + return super.getAttribute(name); + } + + public void updateNodeState() + { + String oldRole = _role; + long oldJoinTime = _joinTime; + long oldTransactionId = _lastTransactionId; + + try + { + //TODO: updateNodeState is called from ReplicatedEnvironmentFacade to call getRemoteNodeState. Odd!!! + NodeState state = _replicatedEnvironmentFacade.getRemoteNodeState(_replicationNode); + _role = state.getNodeState().name(); + _joinTime = state.getJoinTime(); + _lastTransactionId = state.getCurrentTxnEndVLSN(); + } + catch (IOException e) + { + _role = com.sleepycat.je.rep.ReplicatedEnvironment.State.UNKNOWN.name(); + LOGGER.warn("Cannot connect to node " + _replicationNode.getName() + " from " + _groupName); + } + catch (ServiceConnectFailedException e) + { + _role = com.sleepycat.je.rep.ReplicatedEnvironment.State.UNKNOWN.name(); + LOGGER.warn("Cannot retrieve the node details for node " + _replicationNode.getName() + " from " + _groupName); + } + + if (!_role.equals(oldRole)) + { + attributeSet(ROLE, oldRole, _role); + } + + if (_joinTime != oldJoinTime) + { + attributeSet(JOIN_TIME, oldJoinTime, _joinTime); + } + + if (_lastTransactionId != oldTransactionId) + { + attributeSet(LAST_KNOWN_REPLICATION_TRANSACTION_ID, oldTransactionId, _lastTransactionId); + } + } + + @Override + public Collection<String> getAttributeNames() + { + return ReplicationNode.AVAILABLE_ATTRIBUTES; + } + + @Override + public void changeAttributes(Map<String, Object> attributes) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + checkWhetherImmutableAttributeChanged(attributes); + Map<String, Object> convertedAttributes = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + + if (convertedAttributes.containsKey(ROLE)) + { + String currentRole = (String)getAttribute(ROLE); + if (!ReplicatedEnvironment.State.REPLICA.name().equals(currentRole)) + { + throw new IllegalConfigurationException("Cannot transfer mastership when not a replica"); + } + + String role = (String)convertedAttributes.get(ROLE); + + if (ReplicatedEnvironment.State.MASTER.name().equals(role) ) + { + try + { + String nodeName = getName(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Trying to transfer master to " + nodeName); + } + + _replicatedEnvironmentFacade.transferMasterAsynchronously(nodeName); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("The mastership has been transfered to " + nodeName); + } + } + catch(Exception e) + { + throw new IllegalConfigurationException("Cannot transfer mastership to " + getName(), e); + } + } + else + { + throw new IllegalConfigurationException("Changing role to other value then " + + ReplicatedEnvironment.State.MASTER.name() + " is unsupported"); + } + } + + super.changeAttributes(convertedAttributes); + } + + private void checkWhetherImmutableAttributeChanged(Map<String, Object> attributes) + { + Set<String> immutableAttributeNames = new HashSet<String>(getAttributeNames()); + immutableAttributeNames.remove(ROLE); + for (String attributeName : immutableAttributeNames) + { + if (attributes.containsKey(attributeName)) + { + // the name is appended into attributes map in REST layer + if (attributeName.equals(NAME) && getName().equals(attributes.get(NAME))) + { + continue; + } + throw new IllegalConfigurationException("Cannot change value of immutable attribute " + attributeName); + } + } + } + + com.sleepycat.je.rep.ReplicationNode getReplicationNode() + { + return _replicationNode; + } + +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/RemoteReplicationNodeFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/RemoteReplicationNodeFactory.java new file mode 100644 index 0000000000..da235f5616 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/RemoteReplicationNodeFactory.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +public interface RemoteReplicationNodeFactory +{ + RemoteReplicationNode create(com.sleepycat.je.rep.ReplicationNode jeNode, ReplicatedEnvironmentFacade environmentFacade); + + long getRemoteNodeMonitorInterval(); +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacade.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacade.java new file mode 100644 index 0000000000..dc0b1b2ff1 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacade.java @@ -0,0 +1,1143 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import static org.apache.qpid.server.model.ReplicationNode.COALESCING_SYNC; +import static org.apache.qpid.server.model.ReplicationNode.DESIGNATED_PRIMARY; +import static org.apache.qpid.server.model.ReplicationNode.DURABILITY; +import static org.apache.qpid.server.model.ReplicationNode.GROUP_NAME; +import static org.apache.qpid.server.model.ReplicationNode.HELPER_HOST_PORT; +import static org.apache.qpid.server.model.ReplicationNode.HOST_PORT; +import static org.apache.qpid.server.model.ReplicationNode.PARAMETERS; +import static org.apache.qpid.server.model.ReplicationNode.PRIORITY; +import static org.apache.qpid.server.model.ReplicationNode.QUORUM_OVERRIDE; +import static org.apache.qpid.server.model.ReplicationNode.REPLICATION_PARAMETERS; +import static org.apache.qpid.server.model.ReplicationNode.STORE_PATH; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.replication.ReplicationGroupListener; +import org.apache.qpid.server.store.berkeleydb.CoalescingCommiter; +import org.apache.qpid.server.store.berkeleydb.Committer; +import org.apache.qpid.server.store.berkeleydb.EnvironmentFacade; +import org.apache.qpid.server.store.berkeleydb.LoggingAsyncExceptionListener; +import org.apache.qpid.server.util.DaemonThreadFactory; + +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Durability; +import com.sleepycat.je.Environment; +import com.sleepycat.je.EnvironmentConfig; +import com.sleepycat.je.EnvironmentFailureException; +import com.sleepycat.je.Transaction; +import com.sleepycat.je.rep.InsufficientLogException; +import com.sleepycat.je.rep.InsufficientReplicasException; +import com.sleepycat.je.rep.NetworkRestore; +import com.sleepycat.je.rep.NetworkRestoreConfig; +import com.sleepycat.je.rep.NodeState; +import com.sleepycat.je.rep.RepInternal; +import com.sleepycat.je.rep.ReplicatedEnvironment; +import com.sleepycat.je.rep.ReplicationConfig; +import com.sleepycat.je.rep.ReplicationGroup; +import com.sleepycat.je.rep.ReplicationMutableConfig; +import com.sleepycat.je.rep.ReplicationNode; +import com.sleepycat.je.rep.RestartRequiredException; +import com.sleepycat.je.rep.StateChangeEvent; +import com.sleepycat.je.rep.StateChangeListener; +import com.sleepycat.je.rep.util.DbPing; +import com.sleepycat.je.rep.util.ReplicationGroupAdmin; +import com.sleepycat.je.rep.utilint.ServiceDispatcher.ServiceConnectFailedException; +import com.sleepycat.je.rep.vlsn.VLSNRange; +import com.sleepycat.je.utilint.PropUtil; +import com.sleepycat.je.utilint.VLSN; + +public class ReplicatedEnvironmentFacade implements EnvironmentFacade, StateChangeListener +{ + public static final String GROUP_CHECK_INTERVAL_PROPERTY_NAME = "qpid.bdb.ha.group_check_interval"; + public static final String MASTER_TRANSFER_TIMEOUT_PROPERTY_NAME = "qpid.bdb.ha.master_transfer_interval"; + + private static final Logger LOGGER = Logger.getLogger(ReplicatedEnvironmentFacade.class); + private static final long DEFAULT_GROUP_CHECK_INTERVAL = 1000l; + private static final long GROUP_CHECK_INTERVAL = Long.getLong(GROUP_CHECK_INTERVAL_PROPERTY_NAME, DEFAULT_GROUP_CHECK_INTERVAL); + + private static final int DEFAULT_MASTER_TRANSFER_TIMEOUT = 1000 * 60; + + public static final int MASTER_TRANSFER_TIMEOUT = Integer.getInteger(MASTER_TRANSFER_TIMEOUT_PROPERTY_NAME, DEFAULT_MASTER_TRANSFER_TIMEOUT); + + public static final String DB_PING_SOCKET_TIMEOUT_PROPERTY_NAME = "qpid.bdb.ha.db_ping_socket_timeout"; + private static final int DEFAULT_DB_PING_SOCKET_TIMEOUT = 1000; + + private static final int DB_PING_SOCKET_TIMEOUT = Integer.getInteger(DB_PING_SOCKET_TIMEOUT_PROPERTY_NAME, DEFAULT_DB_PING_SOCKET_TIMEOUT); + + @SuppressWarnings("serial") + private static final Map<String, String> REPCONFIG_DEFAULTS = Collections.unmodifiableMap(new HashMap<String, String>() + {{ + /** + * Parameter decreased as the 24h default may lead very large log files for most users. + */ + put(ReplicationConfig.REP_STREAM_TIMEOUT, "1 h"); + /** + * Parameter increased as the 5 s default may lead to spurious timeouts. + */ + put(ReplicationConfig.REPLICA_ACK_TIMEOUT, "15 s"); + /** + * Parameter increased as the 10 s default may lead to spurious timeouts. + */ + put(ReplicationConfig.INSUFFICIENT_REPLICAS_TIMEOUT, "20 s"); + /** + * Parameter increased as the 10 h default may cause user confusion. + */ + put(ReplicationConfig.ENV_SETUP_TIMEOUT, "15 min"); + /** + * Parameter changed from default true so we adopt immediately adopt the new behaviour early. False + * is scheduled to become default after JE 5.0.48. + */ + put(ReplicationConfig.PROTOCOL_OLD_STRING_ENCODING, Boolean.FALSE.toString()); + /** + * Parameter decreased as a default 5min interval may lead to bigger data losses on Node + * with NO_SYN durability in case if such Node crushes. + */ + put(ReplicationConfig.LOG_FLUSH_TASK_INTERVAL, "1 min"); + + /** + * Timeout to transit into UNKNOWN state if the majority is not available. + * By default it is switched off. + */ + put(ReplicationConfig.ENV_UNKNOWN_STATE_TIMEOUT, "5 s"); + }}); + + public static final String TYPE = "BDB-HA"; + + + private final LocalReplicationNode _replicationNode; + private final Durability _durability; + private final Boolean _coalescingSync; + private final String _prettyGroupNodeName; + private final File _environmentDirectory; + + private final ExecutorService _environmentJobExecutor; + private final ScheduledExecutorService _groupChangeExecutor; + private final AtomicReference<State> _state = new AtomicReference<State>(State.OPENING); + private final ConcurrentMap<String, DatabaseHolder> _databases = new ConcurrentHashMap<String, DatabaseHolder>(); + private final ConcurrentMap<String, RemoteReplicationNode> _remoteReplicationNodes = new ConcurrentHashMap<String, RemoteReplicationNode>(); + private final RemoteReplicationNodeFactory _remoteReplicationNodeFactory; + private final AtomicReference<ReplicationGroupListener> _replicationGroupListener = new AtomicReference<ReplicationGroupListener>(); + private final AtomicReference<StateChangeListener> _stateChangeListener = new AtomicReference<StateChangeListener>(); + + private volatile ReplicatedEnvironment _environment; + private long _joinTime; + + public ReplicatedEnvironmentFacade(LocalReplicationNode replicationNode, RemoteReplicationNodeFactory remoteReplicationNodeFactory) + { + _environmentDirectory = new File((String)replicationNode.getAttribute(STORE_PATH)); + if (!_environmentDirectory.exists()) + { + if (!_environmentDirectory.mkdirs()) + { + throw new IllegalArgumentException("Environment path " + _environmentDirectory + " could not be read or created. " + + "Ensure the path is correct and that the permissions are correct."); + } + } + + _replicationNode = replicationNode; + + _durability = Durability.parse((String)_replicationNode.getAttribute(DURABILITY)); + _coalescingSync = (Boolean)_replicationNode.getAttribute(COALESCING_SYNC); + _prettyGroupNodeName = (String)_replicationNode.getAttribute(GROUP_NAME) + ":" + _replicationNode.getName(); + + // we relay on this executor being single-threaded as we need to restart and mutate the environment in one thread + _environmentJobExecutor = Executors.newSingleThreadExecutor(new DaemonThreadFactory("Environment-" + _prettyGroupNodeName)); + _groupChangeExecutor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors() + 1, new DaemonThreadFactory("Group-Change-Learner:" + _prettyGroupNodeName)); + + _remoteReplicationNodeFactory = remoteReplicationNodeFactory; + _groupChangeExecutor.scheduleWithFixedDelay(new GroupChangeLearner(), 0, GROUP_CHECK_INTERVAL, TimeUnit.MILLISECONDS); + _groupChangeExecutor.schedule(new RemoteNodeStateLearner(), _remoteReplicationNodeFactory.getRemoteNodeMonitorInterval(), TimeUnit.MILLISECONDS); + + // create environment in a separate thread to avoid renaming of the current thread by JE + _environment = createEnvironment(true); + populateExistingRemoteReplicationNodes(); + } + + @Override + public void commit(final Transaction tx) throws AMQStoreException + { + try + { + // Using commit() instead of commitNoSync() for the HA store to allow + // the HA durability configuration to influence resulting behaviour. + tx.commit(); + } + catch (DatabaseException de) + { + throw handleDatabaseException("Got DatabaseException on commit, closing environment", de); + } + } + + @Override + public void close() + { + if (_state.compareAndSet(State.OPENING, State.CLOSING) || + _state.compareAndSet(State.OPEN, State.CLOSING) || + _state.compareAndSet(State.RESTARTING, State.CLOSING) ) + { + try + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Closing replicated environment facade for " + _prettyGroupNodeName); + } + + _environmentJobExecutor.shutdown(); + _groupChangeExecutor.shutdown(); + closeDatabases(); + closeEnvironment(); + } + finally + { + _state.compareAndSet(State.CLOSING, State.CLOSED); + } + } + } + + @Override + public AMQStoreException handleDatabaseException(String contextMessage, final DatabaseException dbe) + { + boolean restart = (dbe instanceof InsufficientReplicasException || dbe instanceof InsufficientReplicasException || dbe instanceof RestartRequiredException); + if (restart) + { + if (_state.compareAndSet(State.OPEN, State.RESTARTING)) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Environment restarting due to exception " + dbe.getMessage(), dbe); + } + + _environmentJobExecutor.execute(new Runnable() + { + @Override + public void run() + { + try + { + restartEnvironment(dbe); + } + catch (Exception e) + { + LOGGER.error("Exception on environment restart", e); + } + } + }); + + } + else + { + LOGGER.info("Cannot restart environment because of facade state: " + _state.get()); + } + } + return new AMQStoreException(contextMessage, dbe); + } + + @Override + public void openDatabases(DatabaseConfig dbConfig, String... databaseNames) + { + if (_state.get() != State.OPEN) + { + throw new IllegalStateException("Environment facade is not in opened state"); + } + + if (!_environment.isValid()) + { + throw new IllegalStateException("Environment is not valid"); + } + + if (_environment.getState() != ReplicatedEnvironment.State.MASTER) + { + throw new IllegalStateException("Databases can only be opened on Master node"); + } + + for (String databaseName : databaseNames) + { + _databases.put(databaseName, new DatabaseHolder(dbConfig)); + } + for (String databaseName : databaseNames) + { + DatabaseHolder holder = _databases.get(databaseName); + openDatabaseInternally(databaseName, holder); + } + } + + private void openDatabaseInternally(String databaseName, DatabaseHolder holder) + { + Database database = _environment.openDatabase(null, databaseName, holder.getConfig()); + holder.setDatabase(database); + } + + @Override + public Database getOpenDatabase(String name) + { + if (_state.get() != State.OPEN) + { + throw new IllegalStateException("Environment facade is not in opened state"); + } + + if (!_environment.isValid()) + { + throw new IllegalStateException("Environment is not valid"); + } + DatabaseHolder databaseHolder = _databases.get(name); + if (databaseHolder == null) + { + throw new IllegalArgumentException("Database with name '" + name + "' has never been requested to be opened"); + } + Database database = databaseHolder.getDatabase(); + if (database == null) + { + throw new IllegalArgumentException("Database with name '" + name + "' has not been opened"); + } + return database; + } + + @Override + public String getStoreLocation() + { + return _environmentDirectory.getAbsolutePath(); + } + + @Override + public void stateChange(final StateChangeEvent stateChangeEvent) + { + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("The node '" + _prettyGroupNodeName + "' state is " + stateChangeEvent.getState()); + } + + if (_state.get() != State.CLOSING && _state.get() != State.CLOSED) + { + _groupChangeExecutor.submit(new Runnable() + { + @Override + public void run() + { + stateChanged(stateChangeEvent); + } + }); + } + } + + private void stateChanged(StateChangeEvent stateChangeEvent) + { + ReplicatedEnvironment.State state = stateChangeEvent.getState(); + + if (state == ReplicatedEnvironment.State.REPLICA || state == ReplicatedEnvironment.State.MASTER) + { + if (_state.compareAndSet(State.OPENING, State.OPEN) || _state.compareAndSet(State.RESTARTING, State.OPEN)) + { + LOGGER.info("The environment facade is in open state for node " + _prettyGroupNodeName); + _joinTime = System.currentTimeMillis(); + } + } + + if (state == ReplicatedEnvironment.State.MASTER) + { + reopenDatabases(); + } + + StateChangeListener listener = _stateChangeListener.get(); + if (listener != null) + { + listener.stateChange(stateChangeEvent); + } + } + + private void reopenDatabases() + { + DatabaseConfig pingDbConfig = new DatabaseConfig(); + pingDbConfig.setTransactional(true); + pingDbConfig.setAllowCreate(true); + + _databases.putIfAbsent(DatabasePinger.PING_DATABASE_NAME, new DatabaseHolder(pingDbConfig)); + + for (Map.Entry<String, DatabaseHolder> entry : _databases.entrySet()) + { + openDatabaseInternally(entry.getKey(), entry.getValue()); + } + } + + public String getGroupName() + { + return (String)_replicationNode.getAttribute(GROUP_NAME); + } + + public String getNodeName() + { + return _replicationNode.getName(); + } + + public String getHostPort() + { + return (String)_replicationNode.getAttribute(HOST_PORT); + } + + public String getHelperHostPort() + { + return (String)_replicationNode.getAttribute(HELPER_HOST_PORT); + } + + public String getDurability() + { + return _durability.toString(); + } + + public boolean isCoalescingSync() + { + return _coalescingSync; + } + + public String getNodeState() + { + ReplicatedEnvironment.State state = _environment.getState(); + return state.toString(); + } + + public void removeNodeFromGroup(final String nodeName) + { + createReplicationGroupAdmin().removeMember(nodeName); + } + + public boolean isDesignatedPrimary() + { + return _environment.getRepMutableConfig().getDesignatedPrimary(); + } + + public Future<Void> setDesignatedPrimary(final boolean isPrimary) + { + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Submitting a job to set designated primary on " + _prettyGroupNodeName + " to " + isPrimary); + } + + return _environmentJobExecutor.submit(new Callable<Void>() + { + @Override + public Void call() + { + setDesignatedPrimaryInternal(isPrimary); + return null; + } + }); + } + + private void setDesignatedPrimaryInternal(final boolean isPrimary) + { + try + { + final ReplicationMutableConfig oldConfig = _environment.getRepMutableConfig(); + final ReplicationMutableConfig newConfig = oldConfig.setDesignatedPrimary(isPrimary); + _environment.setRepMutableConfig(newConfig); + + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Node " + _prettyGroupNodeName + " successfully set designated primary : " + isPrimary); + } + } + catch (Exception e) + { + LOGGER.error("Cannot set designated primary to " + isPrimary + " on node " + _prettyGroupNodeName, e); + } + } + + int getPriority() + { + ReplicationMutableConfig repConfig = _environment.getRepMutableConfig(); + return repConfig.getNodePriority(); + } + + public Future<Void> setPriority(final int priority) + { + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Submitting a job to set priority on " + _prettyGroupNodeName + " to " + priority); + } + + return _environmentJobExecutor.submit(new Callable<Void>() + { + @Override + public Void call() + { + setPriorityInternal(priority); + return null; + } + }); + } + + private void setPriorityInternal(int priority) + { + try + { + final ReplicationMutableConfig oldConfig = _environment.getRepMutableConfig(); + final ReplicationMutableConfig newConfig = oldConfig.setNodePriority(priority); + _environment.setRepMutableConfig(newConfig); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Node " + _prettyGroupNodeName + " priority has been changed to " + priority); + } + } + catch (Exception e) + { + LOGGER.error("Cannot set priority to " + priority + " on node " + _prettyGroupNodeName, e); + } + } + + int getElectableGroupSizeOverride() + { + ReplicationMutableConfig repConfig = _environment.getRepMutableConfig(); + return repConfig.getElectableGroupSizeOverride(); + } + + public Future<Void> setElectableGroupSizeOverride(final int electableGroupOverride) + { + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Submitting a job to set electable group override on " + _prettyGroupNodeName + " to " + electableGroupOverride); + } + + return _environmentJobExecutor.submit(new Callable<Void>() + { + @Override + public Void call() + { + setElectableGroupSizeOverrideInternal(electableGroupOverride); + return null; + } + }); + } + + private void setElectableGroupSizeOverrideInternal(int electableGroupOverride) + { + try + { + final ReplicationMutableConfig oldConfig = _environment.getRepMutableConfig(); + final ReplicationMutableConfig newConfig = oldConfig.setElectableGroupSizeOverride(electableGroupOverride); + _environment.setRepMutableConfig(newConfig); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Node " + _prettyGroupNodeName + " electable group size override has been changed to " + electableGroupOverride); + } + } + catch (Exception e) + { + LOGGER.error("Cannot set electable group size to " + electableGroupOverride + " on node " + _prettyGroupNodeName, e); + } + } + + + public long getJoinTime() + { + return _joinTime ; + } + + public long getLastKnownReplicationTransactionId() + { + if (_state.get() == State.OPEN) + { + VLSNRange range = RepInternal.getRepImpl(_environment).getVLSNIndex().getRange(); + VLSN lastTxnEnd = range.getLastTxnEnd(); + return lastTxnEnd.getSequence(); + } + else + { + return -1L; + } + } + + public void transferMasterToSelfAsynchronously() + { + final String nodeName = getNodeName(); + transferMasterAsynchronously(nodeName); + } + + public void transferMasterAsynchronously(final String nodeName) + { + _groupChangeExecutor.submit(new Runnable() + { + @Override + public void run() + { + try + { + ReplicationGroupAdmin admin = createReplicationGroupAdmin(); + String newMaster = admin.transferMaster(Collections.singleton(nodeName), MASTER_TRANSFER_TIMEOUT, TimeUnit.MILLISECONDS, true); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("The mastership has been transfered to " + newMaster); + } + } + catch (DatabaseException e) + { + LOGGER.warn("Exception on transfering the mastership to " + _prettyGroupNodeName + + " Master transfer timeout : " + MASTER_TRANSFER_TIMEOUT, e); + } + } + }); + } + + public ReplicatedEnvironment getEnvironment() + { + return _environment; + } + + public State getFacadeState() + { + return _state.get(); + } + + public void setReplicationGroupListener(ReplicationGroupListener replicationGroupListener) + { + if (_replicationGroupListener.compareAndSet(null, replicationGroupListener)) + { + notifyExistingRemoteReplicationNodes(replicationGroupListener); + } + else + { + throw new IllegalStateException("ReplicationGroupListener is already set on " + _prettyGroupNodeName); + } + } + + public void setStateChangeListener(StateChangeListener stateChangeListener) + { + if (_stateChangeListener.compareAndSet(null, stateChangeListener)) + { + _environment.setStateChangeListener(this); + } + else + { + throw new IllegalStateException("StateChangeListener is already set on " + _prettyGroupNodeName); + } + } + + private void populateExistingRemoteReplicationNodes() + { + ReplicationGroup group = _environment.getGroup(); + Set<ReplicationNode> nodes = new HashSet<ReplicationNode>(group.getElectableNodes()); + String localNodeName = getNodeName(); + + for (ReplicationNode replicationNode : nodes) + { + String discoveredNodeName = replicationNode.getName(); + if (!discoveredNodeName.equals(localNodeName)) + { + RemoteReplicationNode remoteNode = _remoteReplicationNodeFactory.create(replicationNode, this); + + _remoteReplicationNodes.put(replicationNode.getName(), remoteNode); + } + } + } + + private void notifyExistingRemoteReplicationNodes(ReplicationGroupListener listener) + { + for (org.apache.qpid.server.model.ReplicationNode value : _remoteReplicationNodes.values()) + { + listener.onReplicationNodeRecovered(value); + } + } + + private ReplicationGroupAdmin createReplicationGroupAdmin() + { + final Set<InetSocketAddress> helpers = new HashSet<InetSocketAddress>(); + for (RemoteReplicationNode node : _remoteReplicationNodes.values()) + { + helpers.add(node.getReplicationNode().getSocketAddress()); + } + + //TODO: refactor this into a method on LocalReplicationNode + String hostPort = (String)_replicationNode.getAttribute(org.apache.qpid.server.model.ReplicationNode.HOST_PORT); + String[] tokens = hostPort.split(":"); + helpers.add(new InetSocketAddress(tokens[0], Integer.parseInt(tokens[1]))); + + return new ReplicationGroupAdmin((String)_replicationNode.getAttribute(GROUP_NAME), helpers); + } + + private void closeEnvironment() + { + // Clean the log before closing. This makes sure it doesn't contain + // redundant data. Closing without doing this means the cleaner may not + // get a chance to finish. + try + { + if (_environment.isValid()) + { + _environment.cleanLog(); + } + } + finally + { + _environment.close(); + _environment = null; + } + } + + private void restartEnvironment(DatabaseException dbe) + { + LOGGER.info("Restarting environment"); + + closeEnvironmentSafely(); + + _environment = createEnvironment(false); + + if (_stateChangeListener.get() != null) + { + _environment.setStateChangeListener(this); + } + + LOGGER.info("Environment is restarted"); + } + + private void closeEnvironmentSafely() + { + Environment environment = _environment; + if (environment != null) + { + try + { + if (environment.isValid()) + { + try + { + closeDatabases(); + } + catch(Exception e) + { + LOGGER.warn("Ignoring an exception whilst closing databases", e); + } + } + environment.close(); + } + catch (EnvironmentFailureException efe) + { + LOGGER.warn("Ignoring an exception whilst closing environment", efe); + } + } + } + + private void closeDatabases() + { + RuntimeException firstThrownException = null; + for (Map.Entry<String, DatabaseHolder> entry : _databases.entrySet()) + { + DatabaseHolder databaseHolder = entry.getValue(); + Database database = databaseHolder.getDatabase(); + if (database != null) + { + try + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Closing database " + entry.getKey() + " on " + _prettyGroupNodeName); + } + + database.close(); + } + catch(RuntimeException e) + { + LOGGER.error("Failed to close database on " + _prettyGroupNodeName, e); + if (firstThrownException == null) + { + firstThrownException = e; + } + } + finally + { + databaseHolder.setDatabase(null); + } + } + } + if (firstThrownException != null) + { + throw firstThrownException; + } + } + + @SuppressWarnings("unchecked") + private ReplicatedEnvironment createEnvironment(boolean createEnvironmentInSeparateThread) + { + String groupName = (String)_replicationNode.getActualAttribute(GROUP_NAME); + String helperHostPort = (String)_replicationNode.getActualAttribute(HELPER_HOST_PORT); + String hostPort = (String)_replicationNode.getActualAttribute(HOST_PORT); + Map<String, String> environmentParameters = (Map<String, String>)_replicationNode.getActualAttribute(PARAMETERS); + Map<String, String> replicationEnvironmentParameters = (Map<String, String>)_replicationNode.getActualAttribute(REPLICATION_PARAMETERS); + Boolean designatedPrimary = (Boolean)_replicationNode.getActualAttribute(DESIGNATED_PRIMARY); + Integer priority = (Integer)_replicationNode.getActualAttribute(PRIORITY); + Integer quorumOverride = (Integer)_replicationNode.getActualAttribute(QUORUM_OVERRIDE); + + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Creating environment"); + LOGGER.info("Environment path " + _environmentDirectory.getAbsolutePath()); + LOGGER.info("Group name " + groupName); + LOGGER.info("Node name " + _replicationNode.getName()); + LOGGER.info("Node host port " + hostPort); + LOGGER.info("Helper host port " + helperHostPort); + LOGGER.info("Durability " + _durability); + LOGGER.info("Coalescing sync " + _coalescingSync); + LOGGER.info("Designated primary (applicable to 2 node case only) " + designatedPrimary); + LOGGER.info("Node priority " + priority); + LOGGER.info("Quorum override " + quorumOverride); + } + + Map<String, String> replicationEnvironmentSettings = new HashMap<String, String>(REPCONFIG_DEFAULTS); + if (replicationEnvironmentParameters != null && !replicationEnvironmentParameters.isEmpty()) + { + replicationEnvironmentSettings.putAll(replicationEnvironmentParameters); + } + Map<String, String> environmentSettings = new HashMap<String, String>(EnvironmentFacade.ENVCONFIG_DEFAULTS); + if (environmentParameters != null && !environmentParameters.isEmpty()) + { + environmentSettings.putAll(environmentParameters); + } + + ReplicationConfig replicationConfig = new ReplicationConfig(groupName, _replicationNode.getName(), hostPort); + replicationConfig.setHelperHosts(helperHostPort); + replicationConfig.setDesignatedPrimary(designatedPrimary); + replicationConfig.setNodePriority(priority); + replicationConfig.setElectableGroupSizeOverride(quorumOverride); + + for (Map.Entry<String, String> configItem : replicationEnvironmentSettings.entrySet()) + { + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Setting ReplicationConfig key " + configItem.getKey() + " to '" + configItem.getValue() + "'"); + } + replicationConfig.setConfigParam(configItem.getKey(), configItem.getValue()); + } + + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setAllowCreate(true); + envConfig.setTransactional(true); + envConfig.setExceptionListener(new LoggingAsyncExceptionListener()); + envConfig.setDurability(_durability); + + for (Map.Entry<String, String> configItem : environmentSettings.entrySet()) + { + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Setting EnvironmentConfig key " + configItem.getKey() + " to '" + configItem.getValue() + "'"); + } + envConfig.setConfigParam(configItem.getKey(), configItem.getValue()); + } + + if (createEnvironmentInSeparateThread) + { + return createEnvironmentInSeparateThread(_environmentDirectory, envConfig, replicationConfig); + } + else + { + return createEnvironment(_environmentDirectory, envConfig, replicationConfig); + } + } + + private ReplicatedEnvironment createEnvironmentInSeparateThread(final File environmentPathFile, final EnvironmentConfig envConfig, + final ReplicationConfig replicationConfig) + { + Future<ReplicatedEnvironment> environmentFuture = _environmentJobExecutor.submit(new Callable<ReplicatedEnvironment>(){ + @Override + public ReplicatedEnvironment call() throws Exception + { + String originalThreadName = Thread.currentThread().getName(); + try + { + return createEnvironment(environmentPathFile, envConfig, replicationConfig); + } + finally + { + Thread.currentThread().setName(originalThreadName); + } + }}); + + long setUpTimeOutMillis = PropUtil.parseDuration(replicationConfig.getConfigParam(ReplicationConfig.ENV_SETUP_TIMEOUT)); + try + { + return environmentFuture.get(setUpTimeOutMillis, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + throw new RuntimeException("Environment creation was interrupted", e); + } + catch (ExecutionException e) + { + throw new RuntimeException("Unexpected exception on environment creation", e.getCause()); + } + catch (TimeoutException e) + { + throw new RuntimeException("JE environment has not been created in due time"); + } + } + + private ReplicatedEnvironment createEnvironment(File environmentPathFile, EnvironmentConfig envConfig, + final ReplicationConfig replicationConfig) + { + ReplicatedEnvironment environment = null; + try + { + environment = new ReplicatedEnvironment(environmentPathFile, replicationConfig, envConfig); + } + catch (final InsufficientLogException ile) + { + LOGGER.info("InsufficientLogException thrown and so full network restore required", ile); + NetworkRestore restore = new NetworkRestore(); + NetworkRestoreConfig config = new NetworkRestoreConfig(); + config.setRetainLogFiles(false); + restore.execute(ile, config); + environment = new ReplicatedEnvironment(environmentPathFile, replicationConfig, envConfig); + } + return environment; + } + + @Override + public Committer createCommitter(String name) + { + if (_coalescingSync) + { + return new CoalescingCommiter(name, this); + } + else + { + return Committer.IMMEDIATE_FUTURE_COMMITTER; + } + } + + public NodeState getRemoteNodeState(ReplicationNode repNode) throws IOException, ServiceConnectFailedException + { + if (repNode == null) + { + throw new IllegalArgumentException("Node cannot be null"); + } + return new DbPing(repNode, (String)_replicationNode.getAttribute(GROUP_NAME), DB_PING_SOCKET_TIMEOUT).getNodeState(); + } + + // For testing only + int getNumberOfElectableGroupMembers() + { + return _environment.getGroup().getElectableNodes().size(); + } + + private final class GroupChangeLearner implements Runnable + { + @Override + public void run() + { + String groupName = (String)_replicationNode.getAttribute(GROUP_NAME); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Checking for changes in the group " + groupName); + } + + ReplicatedEnvironment env = _environment; + ReplicationGroupListener replicationGroupListener = _replicationGroupListener.get(); + if (env != null && env.isValid()) + { + ReplicationGroup group = env.getGroup(); + Set<ReplicationNode> nodes = new HashSet<ReplicationNode>(group.getElectableNodes()); + String localNodeName = getNodeName(); + + Map<String, org.apache.qpid.server.model.ReplicationNode> removalMap = new HashMap<String, org.apache.qpid.server.model.ReplicationNode>(_remoteReplicationNodes); + for (ReplicationNode replicationNode : nodes) + { + String discoveredNodeName = replicationNode.getName(); + if (!discoveredNodeName.equals(localNodeName)) + { + if (!_remoteReplicationNodes.containsKey(discoveredNodeName)) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Remote replication node added '" + replicationNode + "' to '" + groupName + "'"); + } + + RemoteReplicationNode remoteNode = _remoteReplicationNodeFactory.create(replicationNode, ReplicatedEnvironmentFacade.this); + + _remoteReplicationNodes.put(discoveredNodeName, remoteNode); + + if (replicationGroupListener != null) + { + replicationGroupListener.onReplicationNodeAddedToGroup(remoteNode); + } + } + else + { + removalMap.remove(discoveredNodeName); + } + } + } + + if (!removalMap.isEmpty()) + { + for (Map.Entry<String, org.apache.qpid.server.model.ReplicationNode> replicationNodeEntry : removalMap.entrySet()) + { + String replicationNodeName = replicationNodeEntry.getKey(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Remote replication node removed '" + replicationNodeName + "' from '" + groupName + "'"); + } + _remoteReplicationNodes.remove(replicationNodeName); + if (replicationGroupListener != null) + { + replicationGroupListener.onReplicationNodeRemovedFromGroup(replicationNodeEntry.getValue()); + } + } + } + } + } + } + + private class RemoteNodeStateLearner implements Callable<Void> + { + private Map<String, String> _previousGroupState = Collections.emptyMap(); + @Override + public Void call() + { + long remoteNodeMonitorInterval = _remoteReplicationNodeFactory.getRemoteNodeMonitorInterval(); + try + { + Set<Future<Void>> futures = new HashSet<Future<Void>>(); + for (final RemoteReplicationNode node : _remoteReplicationNodes.values()) + { + Future<Void> future = _groupChangeExecutor.submit(new Callable<Void>() + { + @Override + public Void call() + { + node.updateNodeState(); + return null; + } + }); + futures.add(future); + } + + for (Future<Void> future : futures) + { + try + { + future.get(remoteNodeMonitorInterval, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + catch (ExecutionException e) + { + LOGGER.warn("Cannot update node state for group " + (String)_replicationNode.getAttribute(GROUP_NAME), e.getCause()); + } + catch (TimeoutException e) + { + LOGGER.warn("Timeout whilst updating node state for group " + (String)_replicationNode.getAttribute(GROUP_NAME)); + future.cancel(true); + } + } + + if (ReplicatedEnvironment.State.MASTER == _environment.getState()) + { + Map<String, String> currentGroupState = new HashMap<String, String>(); + for (final RemoteReplicationNode node : _remoteReplicationNodes.values()) + { + currentGroupState.put(node.getName(), (String)node.getAttribute(org.apache.qpid.server.model.ReplicationNode.ROLE)); + } + boolean stateChanged = !_previousGroupState.equals(currentGroupState); + _previousGroupState = currentGroupState; + if (stateChanged && State.OPEN == _state.get()) + { + new DatabasePinger().pingDb(ReplicatedEnvironmentFacade.this); + } + } + } + finally + { + _groupChangeExecutor.schedule(this, remoteNodeMonitorInterval, TimeUnit.MILLISECONDS); + } + return null; + } + } + + public static enum State + { + OPENING, + OPEN, + RESTARTING, + CLOSING, + CLOSED + } + + private static class DatabaseHolder + { + private final DatabaseConfig _config; + private Database _database; + + public DatabaseHolder(DatabaseConfig config) + { + _config = config; + } + + public Database getDatabase() + { + return _database; + } + + public void setDatabase(Database database) + { + _database = database; + } + + public DatabaseConfig getConfig() + { + return _config; + } + + @Override + public String toString() + { + return "DatabaseHolder [_config=" + _config + ", _database=" + _database + "]"; + } + + } + +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacadeFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacadeFactory.java new file mode 100644 index 0000000000..1066cca21d --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacadeFactory.java @@ -0,0 +1,94 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import java.util.Collection; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.store.berkeleydb.EnvironmentFacade; +import org.apache.qpid.server.store.berkeleydb.EnvironmentFacadeFactory; + +import com.sleepycat.je.Durability; +import com.sleepycat.je.Durability.SyncPolicy; + +//TODO: Should LocalReplicationNode implement EnvironmentFacadeFactory instead of having this class? +public class ReplicatedEnvironmentFacadeFactory implements EnvironmentFacadeFactory +{ + + @Override + public EnvironmentFacade createEnvironmentFacade(VirtualHost virtualHost, boolean isMessageStore) + { + Collection<ReplicationNode> replicationNodes = virtualHost.getChildren(ReplicationNode.class); + if (replicationNodes == null || replicationNodes.size() != 1) + { + throw new IllegalStateException("Expected exactly one replication node but got " + (replicationNodes==null ? 0 :replicationNodes.size()) + " nodes"); + } + ReplicationNode localNode = replicationNodes.iterator().next(); + if (!(localNode instanceof LocalReplicationNode)) + { + throw new IllegalStateException("Cannot find local replication node among virtual host nodes"); + } + LocalReplicationNode localReplicationNode = (LocalReplicationNode)localNode; + + String durability = (String)localNode.getAttribute(ReplicationNode.DURABILITY); + Boolean coalescingSync = (Boolean)localNode.getAttribute(ReplicationNode.COALESCING_SYNC); + + if (coalescingSync && Durability.parse(durability).getLocalSync() == SyncPolicy.SYNC) + { + throw new IllegalConfigurationException("Coalescing sync cannot be used with master sync policy " + SyncPolicy.SYNC + + "! Please set highAvailability.coalescingSync to false in store configuration."); + } + + ReplicatedEnvironmentFacade facade = new ReplicatedEnvironmentFacade(localReplicationNode, new RemoteReplicationNodeFactoryImpl(virtualHost)); + localReplicationNode.setReplicatedEnvironmentFacade(facade); + return facade; + } + + static class RemoteReplicationNodeFactoryImpl implements RemoteReplicationNodeFactory + { + private VirtualHost _virtualHost; + + public RemoteReplicationNodeFactoryImpl(VirtualHost virtualHost) + { + _virtualHost = virtualHost; + } + + @Override + public RemoteReplicationNode create(com.sleepycat.je.rep.ReplicationNode replicationNode, ReplicatedEnvironmentFacade environmentFacade) + { + return new RemoteReplicationNode(replicationNode, _virtualHost, _virtualHost.getTaskExecutor(), environmentFacade); + } + + @Override + public long getRemoteNodeMonitorInterval() + { + return (Long)_virtualHost.getAttribute(VirtualHost.REMOTE_REPLICATION_NODE_MONITOR_INTERVAL); + } + } + + @Override + public String getType() + { + return ReplicatedEnvironmentFacade.TYPE; + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/StoreUpgrade.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/StoreUpgrade.java index f73e2e5d78..f43a229603 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/StoreUpgrade.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/StoreUpgrade.java @@ -22,6 +22,7 @@ package org.apache.qpid.server.store.berkeleydb.upgrade; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.Environment; + import org.apache.qpid.AMQStoreException; public interface StoreUpgrade diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6.java index a478872ad0..f62ed7d578 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6.java @@ -45,7 +45,6 @@ import org.apache.qpid.server.model.Exchange; import org.apache.qpid.server.model.LifetimePolicy; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.UUIDGenerator; -import org.apache.qpid.server.queue.AMQQueueFactory; import org.apache.qpid.server.queue.QueueArgumentsConverter; import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; import org.apache.qpid.server.store.berkeleydb.FieldTableEncoding; diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/Upgrader.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/Upgrader.java index e769bfae81..7e09ab6cb0 100644 --- a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/Upgrader.java +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/Upgrader.java @@ -21,11 +21,13 @@ package org.apache.qpid.server.store.berkeleydb.upgrade; import com.sleepycat.je.Cursor; + import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import org.apache.log4j.Logger; import org.apache.qpid.AMQStoreException; -import org.apache.qpid.server.store.berkeleydb.AbstractBDBMessageStore; +import org.apache.qpid.server.store.berkeleydb.BDBMessageStore; import com.sleepycat.bind.tuple.IntegerBinding; import com.sleepycat.bind.tuple.LongBinding; @@ -34,11 +36,12 @@ import com.sleepycat.je.DatabaseConfig; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.Environment; -import com.sleepycat.je.LockMode; import com.sleepycat.je.OperationStatus; public class Upgrader { + private static final Logger LOGGER = Logger.getLogger(Upgrader.class); + static final String VERSION_DB_NAME = "DB_VERSION"; private Environment _environment; @@ -64,7 +67,8 @@ public class Upgrader if(versionDb.count() == 0L) { - int sourceVersion = isEmpty ? AbstractBDBMessageStore.VERSION: identifyOldStoreVersion(); + + int sourceVersion = isEmpty ? BDBMessageStore.VERSION: identifyOldStoreVersion(); DatabaseEntry key = new DatabaseEntry(); IntegerBinding.intToEntry(sourceVersion, key); DatabaseEntry value = new DatabaseEntry(); @@ -74,11 +78,17 @@ public class Upgrader } int version = getSourceVersion(versionDb); - if(version > AbstractBDBMessageStore.VERSION) + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Source message store version is " + version); + } + + if(version > BDBMessageStore.VERSION) { throw new AMQStoreException("Database version " + version + " is higher than the most recent known version: " - + AbstractBDBMessageStore.VERSION); + + BDBMessageStore.VERSION); } performUpgradeFromVersion(version, versionDb); } @@ -127,7 +137,7 @@ public class Upgrader void performUpgradeFromVersion(int sourceVersion, Database versionDb) throws AMQStoreException { - while(sourceVersion != AbstractBDBMessageStore.VERSION) + while(sourceVersion != BDBMessageStore.VERSION) { upgrade(sourceVersion, ++sourceVersion); DatabaseEntry key = new DatabaseEntry(); @@ -179,7 +189,7 @@ public class Upgrader private int identifyOldStoreVersion() throws DatabaseException { - int version = 0; + int version = BDBMessageStore.VERSION; for (String databaseName : _environment.getDatabaseNames()) { if (databaseName.contains("_v")) diff --git a/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/addVirtualHost.js b/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/addVirtualHost.js index 44ad5fa57a..4e65201308 100644 --- a/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/addVirtualHost.js +++ b/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/addVirtualHost.js @@ -25,8 +25,19 @@ define(["dojo/_base/xhr", "dijit/registry", "dojo/parser", "dojo/_base/array", + "dojo/json", + "dojo/query", + "dojo/_base/connect", + "dojo/_base/event", + "dojo/store/Memory", + "dojox/grid/DataGrid", + "dojo/data/ObjectStore", "dojo/domReady!"], - function (xhr, dom, construct, win, registry, parser, array) { + function (xhr, dom, construct, win, registry, parser, array, json, query, connect, event, Memory, DataGrid, ObjectStore) { + var nodeFields = ["storePath", "groupName", "nodeName", "state", "role", "hostPort", "helperHostPort", + "coalescingSync", "designatedPrimary", "durability", "priority", + "quorumOverride"]; + return { show: function() { @@ -45,7 +56,190 @@ define(["dojo/_base/xhr", load: function(data) { node.innerHTML = data; parser.parse(node); + for(var i = 0; i < nodeFields.length; i++) + { + that[nodeFields[i]] = registry.byId("formAddVirtualHost.specific." + nodeFields[i]); + } + + that._initSettingsUI(); }}); + }, + save: function() + { + this.success = false; + var that = this; + var virtualHostName = registry.byId("formAddVirtualHost.name").get("value"); + var virtualHostNameEncoded = encodeURIComponent(virtualHostName); + + + // create virtual host in QUIESCED state + var hostData = + { + desiredState: "QUIESCED", + name: virtualHostName, + type: registry.byId("addVirtualHost.type").get("value"), + }; + + xhr.put({url: "rest/virtualhost/" + virtualHostNameEncoded, + sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.stringify(hostData), + load: function(x) { that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + + // if success, create node + if (this.success) + { + var node = {}; + for(var i = 0; i < nodeFields.length; i++) + { + var fieldName = nodeFields[i]; + var widget = this[fieldName]; + if (widget) + { + node[fieldName] = widget.type=="checkbox"? widget.get("checked"): widget.get("value"); + } + } + + node.name = this.nodeName.value; + + var data = this.settingsStore.objectStore.data; + if (data.length > 0 ) + { + var parameters = null; + var replicationParameters = null; + + for(var i=0; i<data.length; i++) + { + if (data[i].name && data[i].value) + { + if (data[i].name.indexOf("je.rep.") == 0) + { + if (replicationParameters == null) + { + replicationParameters = {}; + } + replicationParameters[data[i].name] = data[i].value; + } + else + { + if (parameters == null) + { + parameters = {}; + } + parameters[data[i].name] = data[i].value; + } + } + } + + if (parameters) + { + node["parameters"] = parameters; + } + + if (replicationParameters) + { + node["replicationParameters"] = replicationParameters; + } + } + + xhr.put({url: "rest/replicationnode/" + virtualHostNameEncoded + "/" + encodeURIComponent(this.nodeName.value), + sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.stringify(node), + load: function(x) { that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + + // if success, change virtual host state to ACTIVE + if (this.success) + { + xhr.put({url: "rest/virtualhost/" + virtualHostNameEncoded, + sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.stringify({desiredState: "ACTIVE"}), + load: function(x) { that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + } + } + + return this.success; + }, + + _initSettingsUI: function() + { + var that = this; + var layout = [[ + {'name': 'Name', 'field': 'name', 'width': '200px', 'editable': true}, + {'name': 'Value', 'field': 'value', 'width': 'auto', 'editable': true} + ]]; + this.idGenerator = 0; + this.settingsStore= new ObjectStore({objectStore: new Memory({data: [], idProperty: "id"})}); + this.settings = new DataGrid({ + id: 'formAddVirtualHost.specific.jeSettingsGrid', + store: this.settingsStore, + structure: layout, + rowSelector: '20px', + query:{ name: '*' }, + rowsPerPage:20, + selectionMode: 'multiple', + disable: true}, + "formAddVirtualHost.specific.jeSettings"); + + this.removeSettingButton = registry.byId("formAddVirtualHost.specific.removeSetting"); + this.removeSettingButton.set("disabled", true); + + connect.connect(this.settings.selection, 'onSelected', function(rowIndex){ + var data = that.settings.selection.getSelected(); + that.removeSettingButton.set("disabled",!data.length ); + }); + + this.settings.startup(); + + registry.byId("formAddVirtualHost.specific.addSetting").on("click", function(){ + var rowIndex = that.settingsStore.objectStore.data.length; + var myNewItem = {id: (that.idGenerator++), name: "", value: ""}; + that.settingsStore.objectStore.data.push(myNewItem); + that.settingsStore.objectStore.setData(that.settingsStore.objectStore.data); + that.settings.set("disabled", false); + that.removeSettingButton.set("disabled", false); + that.settings._refresh(); + that.settings.focus.setFocusIndex( rowIndex, 0 ); + that.settings.edit.setEditCell( that.settings.focus.cell, rowIndex ); + }); + + this.removeSettingButton.on("click", function(){ + var items = that.settings.selection.getSelected(); + if(items.length){ + var data = that.settingsStore.objectStore.data; + + array.forEach(items, function(selectedItem){ + if(selectedItem !== null){ + for(var i=0; i<data.length;) + { + if (data[i].id==selectedItem.id) + { + data.splice(i, 1); + break; + } + else + { + i++; + } + } + } + }); + that.settingsStore.objectStore.setData(data); + } + + if (that.settingsStore.objectStore.data.length == 0) + { + that.settings.set("disabled", true); + that.removeSettingButton.set("disabled", true); + } + + that.settings._refresh(); + event.stop(e); + }); } }; }); diff --git a/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/show.js b/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/show.js new file mode 100644 index 0000000000..72d36d57d2 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/resources/js/qpid/management/virtualhost/bdb_ha/show.js @@ -0,0 +1,187 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/string", + "dojox/html/entities", + "dojo/query", + "dijit/registry", + "dojox/grid/EnhancedGrid", + "qpid/common/UpdatableStore", + "qpid/management/UserPreferences", + "dojo/domReady!"], + function (xhr, parser, json, entities, query, registry, EnhancedGrid, UpdatableStore, UserPreferences) { + + var hostFields = ["desiredState", "quiesceOnMasterChange"]; + + var nodeFields = ["storePath", "name", "state", "role", "hostPort", "helperHostPort", + "coalescingSync", "designatedPrimary", "durability", "priority", "quorumOverride"]; + + var buttons = ["activateVirtualHostButton", "quiesceVirtualHostButton", "stopVirtualHostButton", + "editVirtualHostButton", "activateNodeButton", "stopNodeButton","editNodeButton", + "removeNodeButton", "transferMasterButton"]; + + function findNode(nodeClass, containerNode) + { + return query("." + nodeClass, containerNode)[0]; + } + + function convertMap(map) + { + var data =[]; + for(var propName in map) + { + if(map.hasOwnProperty(propName)) + { + data.push({id: propName, name: propName, value: map[propName]}); + } + } + return data; + } + + function BDBHA(containerNode) { + var that = this; + xhr.get({url: "virtualhost/bdb_ha/show.html", + sync: true, + load: function(template) { + containerNode.innerHTML = template; + parser.parse(containerNode); + }}); + this._init(containerNode); + this.designatedPrimaryContainer = findNode("designatedPrimaryContainer", containerNode); + this.priorityContainer = findNode("priorityContainer", containerNode); + this.quorumOverrideContainer = findNode("quorumOverrideContainer", containerNode); + } + + BDBHA.prototype.update=function(data) + { + for(var i = 0; i < hostFields.length; i++) + { + var name = hostFields[i]; + this[name].innerHTML = entities.encode(String(data[name])); + } + var nodes = data.replicationnodes; + if (nodes && nodes.length>0) + { + var localNode = nodes[0]; + for(var i = 0; i < nodeFields.length; i++) + { + var name = nodeFields[i]; + this[name].innerHTML = entities.encode(String(localNode[name])); + } + this.parametersGrid.update(convertMap(localNode.parameters)); + this.parametersGrid.grid._refresh(); + this.replicationParametersGrid.update(convertMap(localNode.replicationParameters)); + this.replicationParametersGrid.grid._refresh(); + if (nodes.length < 3) + { + this.designatedPrimaryContainer.style.display="block"; + this.priorityContainer.style.display="none"; + this.quorumOverrideContainer.style.display="none"; + } + else + { + this.designatedPrimaryContainer.style.display="none"; + this.priorityContainer.style.display="block"; + this.quorumOverrideContainer.style.display="block"; + } + if (this.membersGrid.update(nodes)) + { + this.membersGrid.grid._refresh(); + } + } + }; + + BDBHA.prototype._init = function(containerNode) + { + this._initFields(hostFields, containerNode); + this._initFields(nodeFields, containerNode); + + for(var i = 0; i < buttons.length; i++) + { + var buttonName = buttons[i]; + var buttonNode = findNode(buttonName, containerNode); + if (buttonNode) + { + var buttonWidget = registry.byNode(buttonNode); + if (buttonWidget) + { + this[buttonName] = buttonWidget; + var handler = this["_onClick_" + buttonName]; + if (handler) + { + buttonWidget.on("click", function(evt){ + handler(evt); + }); + } + else + { + buttonWidget.set("disabled", true); + } + } + } + } + + this.membersGrid = new UpdatableStore([], + findNode("cluserNodes", containerNode), + [ + { name: 'Name', field: 'name', width: '10%' }, + { name: 'Role', field: 'role', width: '10%' }, + { name: 'Host and Port', field: 'hostPort', width: '35%' }, + { name: 'Join Time', field: 'joinTime', width: '25%', formatter: function(value){ return value ? UserPreferences.formatDateTime(value) : "";} }, + { name: 'Replication Transaction ID', field: 'lastKnownReplicationTransactionId', width: '20%' } + ], + null, + { + keepSelection: true, + plugins: { + indirectSelection: true + } + }, + EnhancedGrid, true ); + + this.parametersGrid = new UpdatableStore([], + findNode("parameters", containerNode), + [ + { name: 'Name', field: 'name', width: '50%' }, + { name: 'Value', field: 'value', width: '50%' } + ], + null, null, null, true ); + + this.replicationParametersGrid = new UpdatableStore([], + findNode("replicationParameters", containerNode), + [ + { name: 'Name', field: 'name', width: '50%' }, + { name: 'Value', field: 'value', width: '50%' } + ], + null, null, null, true ); + } + + BDBHA.prototype._initFields = function(nodeFields, containerNode) + { + for(var i = 0; i < nodeFields.length; i++) + { + this[nodeFields[i]] = findNode(nodeFields[i], containerNode); + } + } + + return BDBHA; +}); diff --git a/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/add.html b/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/add.html index 31734c818d..d51a5406da 100644 --- a/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/add.html +++ b/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/add.html @@ -26,50 +26,50 @@ <td class="tableContainer-labelCell" style="width: 300px;"><strong>Node Name*: </strong></td> <td class="tableContainer-valueCell"> <input dojoType="dijit/form/ValidationTextBox" id="formAddVirtualHost.specific.nodeName" - required="true" name="haNodeName" placeholder="node name"/> + required="true" name="nodeName" placeholder="node name"/> </td> </tr> <tr> <td class="tableContainer-labelCell" style="width: 300px;"><strong>Replication Group*: </strong></td> <td class="tableContainer-valueCell"> <input dojoType="dijit/form/ValidationTextBox" id="formAddVirtualHost.specific.groupName" - required="true" name="haGroupName" placeholder="group name"/> + required="true" name="groupName" placeholder="group name"/> </td> </tr> <tr> <td class="tableContainer-labelCell" style="width: 300px;"><strong>Node Address*: </strong></td> <td class="tableContainer-valueCell"> - <input dojoType="dijit/form/ValidationTextBox" id="formAddVirtualHost.specific.nodeAddress" - required="true" name="haNodeAddress" data-dojo-props="regExp:'([0-9a-zA-Z.-_]|::)+:[0-9]{1,5}', invalidMessage:'Must be of the form host:port'" placeholder="host:port"/> + <input dojoType="dijit/form/ValidationTextBox" id="formAddVirtualHost.specific.hostPort" + required="true" name="hostPort" data-dojo-props="regExp:'([0-9a-zA-Z.-_]|::)+:[0-9]{1,5}', invalidMessage:'Must be of the form host:port'" placeholder="host:port"/> </td> </tr> <tr> <td class="tableContainer-labelCell" style="width: 300px;"><strong>Helper Address*: </strong></td> <td class="tableContainer-valueCell"> - <input dojoType="dijit/form/ValidationTextBox" id="formAddVirtualHost.specific.helperAddress" - required="true" name="haHelperAddress" data-dojo-props="regExp:'([0-9a-zA-Z.-_]|::)+:[0-9]{1,5}', invalidMessage:'Must be of the form host:port'" placeholder="host:port"/> + <input dojoType="dijit/form/ValidationTextBox" id="formAddVirtualHost.specific.helperHostPort" + required="true" name="helperHostPort" data-dojo-props="regExp:'([0-9a-zA-Z.-_]|::)+:[0-9]{1,5}', invalidMessage:'Must be of the form host:port'" placeholder="host:port"/> </td> </tr> <tr> <td class="tableContainer-labelCell" style="width: 300px;"><strong>Durability: </strong></td> <td class="tableContainer-valueCell"> <input dojoType="dijit/form/ValidationTextBox" id="formAddVirtualHost.specific.haDurability" - name="haDurability" placeholder="NO_SYNC,NO_SYNC,SIMPLE_MAJORITY"/> + name="durability" placeholder="NO_SYNC,NO_SYNC,SIMPLE_MAJORITY"/> </td> </tr> <tr> <td class="tableContainer-labelCell" style="width: 300px;"><strong>Coalesce local sync: </strong></td> <td class="tableContainer-valueCell"> - <input dojoType="dijit/form/CheckBox" id="formAddVirtualHost.specific.haCoalescingSync" + <input dojoType="dijit/form/CheckBox" id="formAddVirtualHost.specific.coalescingSync" checked="true" onchange="require(['dijit/registry', 'dojo/domReady!'], function(registry){ - var checkbox = registry.byId('formAddVirtualHost.specific.haCoalescingSync'); - var hidden = registry.byId('formAddVirtualHost.specific.haCoalescingSyncHidden'); + var checkbox = registry.byId('formAddVirtualHost.specific.coalescingSync'); + var hidden = registry.byId('formAddVirtualHost.specific.coalescingSyncHidden'); hidden.set('value', checkbox.get('checked')); })"/> - <input dojoType="dijit/form/TextBox" id="formAddVirtualHost.specific.haCoalescingSyncHidden" type="hidden" name="haCoalescingSync" value="true"/> + <input dojoType="dijit/form/TextBox" id="formAddVirtualHost.specific.coalescingSyncHidden" type="hidden" name="coalescingSync" value="true"/> </td> </tr> @@ -77,14 +77,20 @@ <tr> <td class="tableContainer-labelCell" style="width: 300px;"><strong>Designated Primary: </strong></td> <td class="tableContainer-valueCell"> - <input dojoType="dijit/form/CheckBox" id="formAddVirtualHost.specific.haDesignatedPrimary" + <input dojoType="dijit/form/CheckBox" id="formAddVirtualHost.specific.designatedPrimary" onchange="require(['dijit/registry', 'dojo/domReady!'], function(registry){ - var checkbox = registry.byId('formAddVirtualHost.specific.haDesignatedPrimary'); - var hidden = registry.byId('formAddVirtualHost.specific.haDesignatedPrimaryHidden'); + var checkbox = registry.byId('formAddVirtualHost.specific.designatedPrimary'); + var hidden = registry.byId('formAddVirtualHost.specific.designatedPrimaryHidden'); hidden.set('value', checkbox.get('checked')); })"/> - <input dojoType="dijit/form/TextBox" id="formAddVirtualHost.specific.haDesignatedPrimaryHidden" type="hidden" name="haDesignatedPrimary" value="false"/> + <input dojoType="dijit/form/TextBox" id="formAddVirtualHost.specific.designatedPrimaryHidden" type="hidden" name="designatedPrimary" value="false"/> </td> </tr> </table> + +<div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Override JE settings', open: false"> + <div id="formAddVirtualHost.specific.jeSettings" style="width: 400px; height: 100px"></div> + <div id='formAddVirtualHost.specific.addSetting' data-dojo-type="dijit.form.Button" data-dojo-props="title:'Add new setting'">Add</div> + <div id='formAddVirtualHost.specific.removeSetting' data-dojo-type="dijit.form.Button" data-dojo-props="title:'Remove the selected settings', disable:true">Remove</div> +</div>
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/show.html b/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/show.html new file mode 100644 index 0000000000..71862bfade --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/resources/virtualhost/bdb_ha/show.html @@ -0,0 +1,117 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you 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. + - + --> +<br/> +<div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Replication Group'"> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Desired State:</div> + <div class="desiredState" style="float:left;">ACTIVE</div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Quiesce On Master Change:</div> + <div style="float:left;"> + <input class="quiesceOnMasterChange" type="checkbox" disabled='disabled'/> + </div> + </div> + <div style="clear:both"></div> + + <div class="dijitDialogPaneActionBar"> + <button data-dojo-type="dijit.form.Button" class="activateVirtualHostButton" data-dojo-props="label: 'Activate', disabled: true" type="button">Activate</button> + <button data-dojo-type="dijit.form.Button" class="quiesceVirtualHostButton" type="button">Quiesce</button> + <button data-dojo-type="dijit.form.Button" class="stopVirtualHostButton" type="button">Stop</button> + <button data-dojo-type="dijit.form.Button" class="editVirtualHostButton" type="button">Edit</button> + </div> +</div> +<br/> + +<div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Node'"> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Node Name:</div> + <div class="name" style="float:left;">N/A</div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Store Path:</div> + <div class="storePath" style="float:left;"></div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">State:</div> + <div class="state" style="float:left;">ACTIVE</div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Role:</div> + <div class="role" style="float:left;">N/A</div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Host and Port:</div> + <div class="hostPort" style="float:left;">N/A</div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Helper Node Host and Port:</div> + <div class="helperHostPort" style="float:left;">N/A</div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Coalescing Sync:</div> + <div class="coalescingSync" style="float:left;">N/A</div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Durability:</div> + <div class="durability" style="float:left;">N/A</div> + </div> + <div style="clear:both" class="designatedPrimaryContainer"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Designated Primary:</div> + <div class="designatedPrimary" style="float:left;">N/A</div> + </div> + <div style="clear:both" class="priorityContainer"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Priority:</div> + <div class="priority" style="float:left;">Never</div> + </div> + <div style="clear:both" class="quorumOverrideContainer"> + <div class="formLabel-labelCell" style="float:left; width: 200px;">Quorum override:</div> + <div style="float:left;"> + <span class="quorumOverride" >N/A</span> + <span style="margin-left: 20px;">[ 0 signifies simple majority ]</span> + </div> + </div> + <div style="clear:both"></div> + <br/> + <div style="clear:both" data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Parameters'"> + <div class="parameters"></div> + </div> + <div style="clear:both"></div> + <br/> + <div style="clear:both" data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Replication Parameters'"> + <div class="replicationParameters"></div> + </div> + <div style="clear:both"></div> + <div class="dijitDialogPaneActionBar"> + <button data-dojo-type="dijit.form.Button" class="activateNodeButton" type="button" data-dojo-props="iconClass: 'dijitEditorIconCreateLink', disabled: true">Activate</button> + <button data-dojo-type="dijit.form.Button" class="stopNodeButton" type="button" data-dojo-props="iconClass: 'dijitEditorIconUnlink'">Stop</button> + <button data-dojo-type="dijit.form.Button" class="editNodeButton" type="button">Edit</button> + </div> +</div> + +<br/> +<div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Group nodes'"> + <div class="cluserNodes"></div> + <div class="cluserNodesToolbar dijitDialogPaneActionBar"> + <button data-dojo-type="dijit.form.Button" class="removeNodeButton" data-dojo-props="iconClass: 'dijitIconDelete'">Remove Node</button> + <button data-dojo-type="dijit.form.Button" class="transferMasterButton" data-dojo-props="iconClass: 'dijitIconConfigure'">Make Node Master</button> + </div> +</div>
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ReplicationNodeFactory b/qpid/java/bdbstore/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ReplicationNodeFactory new file mode 100644 index 0000000000..3050cbbd81 --- /dev/null +++ b/qpid/java/bdbstore/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ReplicationNodeFactory @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +org.apache.qpid.server.store.berkeleydb.replication.LocalReplicationNodeFactory
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBHAMessageStoreTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBHAMessageStoreTest.java deleted file mode 100644 index d7acf27f75..0000000000 --- a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBHAMessageStoreTest.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.qpid.server.store.berkeleydb; - -import java.io.File; -import java.net.InetAddress; - -import java.util.HashMap; -import java.util.Map; -import org.apache.commons.configuration.XMLConfiguration; -import org.apache.qpid.server.configuration.VirtualHostConfiguration; -import org.apache.qpid.server.util.BrokerTestHelper; -import org.apache.qpid.server.virtualhost.VirtualHost; -import org.apache.qpid.test.utils.QpidTestCase; -import org.apache.qpid.util.FileUtils; - -import com.sleepycat.je.Environment; -import com.sleepycat.je.EnvironmentConfig; -import com.sleepycat.je.rep.ReplicatedEnvironment; -import com.sleepycat.je.rep.ReplicationConfig; - -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - -public class BDBHAMessageStoreTest extends QpidTestCase -{ - private static final String TEST_LOG_FILE_MAX = "1000000"; - private static final String TEST_ELECTION_RETRIES = "1000"; - private static final String TEST_NUMBER_OF_THREADS = "10"; - private static final String TEST_ENV_CONSISTENCY_TIMEOUT = "9999999"; - private String _groupName; - private String _workDir; - private int _masterPort; - private String _host; - private XMLConfiguration _configXml; - private VirtualHost _virtualHost; - private org.apache.qpid.server.model.VirtualHost _modelVhost; - - public void setUp() throws Exception - { - super.setUp(); - - _workDir = TMP_FOLDER + File.separator + getName(); - _host = InetAddress.getByName("localhost").getHostAddress(); - _groupName = "group" + getName(); - _masterPort = -1; - - FileUtils.delete(new File(_workDir), true); - _configXml = new XMLConfiguration(); - _modelVhost = mock(org.apache.qpid.server.model.VirtualHost.class); - - - BrokerTestHelper.setUp(); - } - - public void tearDown() throws Exception - { - try - { - if (_virtualHost != null) - { - _virtualHost.close(); - } - FileUtils.delete(new File(_workDir), true); - } - finally - { - BrokerTestHelper.tearDown(); - super.tearDown(); - } - } - - public void testSetSystemConfiguration() throws Exception - { - // create virtual host configuration, registry and host instance - addVirtualHostConfiguration(); - String vhostName = "test" + _masterPort; - VirtualHostConfiguration configuration = new VirtualHostConfiguration(vhostName, _configXml.subset("virtualhosts.virtualhost." + vhostName), BrokerTestHelper.createBrokerMock()); - - _virtualHost = BrokerTestHelper.createVirtualHost(configuration,null,_modelVhost); - BDBHAMessageStore store = (BDBHAMessageStore) _virtualHost.getMessageStore(); - - // test whether JVM system settings were applied - Environment env = store.getEnvironment(); - assertEquals("Unexpected number of cleaner threads", TEST_NUMBER_OF_THREADS, env.getConfig().getConfigParam(EnvironmentConfig.CLEANER_THREADS)); - assertEquals("Unexpected log file max", TEST_LOG_FILE_MAX, env.getConfig().getConfigParam(EnvironmentConfig.LOG_FILE_MAX)); - - ReplicatedEnvironment repEnv = store.getReplicatedEnvironment(); - assertEquals("Unexpected number of elections primary retries", TEST_ELECTION_RETRIES, - repEnv.getConfig().getConfigParam(ReplicationConfig.ELECTIONS_PRIMARY_RETRIES)); - assertEquals("Unexpected number of elections primary retries", TEST_ENV_CONSISTENCY_TIMEOUT, - repEnv.getConfig().getConfigParam(ReplicationConfig.ENV_CONSISTENCY_TIMEOUT)); - } - - private void addVirtualHostConfiguration() throws Exception - { - int port = findFreePort(); - if (_masterPort == -1) - { - _masterPort = port; - } - String nodeName = getNodeNameForNodeAt(port); - - String vhostName = "test" + port; - String vhostPrefix = "virtualhosts.virtualhost." + vhostName; - - _configXml.addProperty("virtualhosts.virtualhost.name", vhostName); - _configXml.addProperty(vhostPrefix + ".type", BDBHAVirtualHostFactory.TYPE); - - when(_modelVhost.getAttribute(eq(_modelVhost.STORE_PATH))).thenReturn(_workDir + File.separator - + port); - when(_modelVhost.getAttribute(eq("haGroupName"))).thenReturn(_groupName); - when(_modelVhost.getAttribute(eq("haNodeName"))).thenReturn(nodeName); - when(_modelVhost.getAttribute(eq("haNodeAddress"))).thenReturn(getNodeHostPortForNodeAt(port)); - when(_modelVhost.getAttribute(eq("haHelperAddress"))).thenReturn(getHelperHostPort()); - - Map<String,String> bdbEnvConfig = new HashMap<String,String>(); - bdbEnvConfig.put(EnvironmentConfig.CLEANER_THREADS, TEST_NUMBER_OF_THREADS); - bdbEnvConfig.put(EnvironmentConfig.LOG_FILE_MAX, TEST_LOG_FILE_MAX); - - when(_modelVhost.getAttribute(eq("bdbEnvironmentConfig"))).thenReturn(bdbEnvConfig); - - Map<String,String> repConfig = new HashMap<String,String>(); - repConfig.put(ReplicationConfig.ELECTIONS_PRIMARY_RETRIES, TEST_ELECTION_RETRIES); - repConfig.put(ReplicationConfig.ENV_CONSISTENCY_TIMEOUT, TEST_ENV_CONSISTENCY_TIMEOUT); - when(_modelVhost.getAttribute(eq("haReplicationConfig"))).thenReturn(repConfig); - - } - - private String getNodeNameForNodeAt(final int bdbPort) - { - return "node" + getName() + bdbPort; - } - - private String getNodeHostPortForNodeAt(final int bdbPort) - { - return _host + ":" + bdbPort; - } - - private String getHelperHostPort() - { - if (_masterPort == -1) - { - throw new IllegalStateException("Helper port not yet assigned."); - } - return _host + ":" + _masterPort; - } -} diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreQuotaEventsTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreQuotaEventsTest.java index 4684358190..7a645a6932 100644 --- a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreQuotaEventsTest.java +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreQuotaEventsTest.java @@ -65,7 +65,7 @@ public class BDBMessageStoreQuotaEventsTest extends MessageStoreQuotaEventsTestB _logger.debug("Applying store specific config. overfull-sze=" + OVERFULL_SIZE + ", underfull-size=" + UNDERFULL_SIZE); Map<String,String> envMap = Collections.singletonMap("je.log.fileMax", MAX_BDB_LOG_SIZE); - when(virtualHost.getAttribute(eq("bdbEnvironmentConfig"))).thenReturn(envMap); + when(virtualHost.getAttribute(eq(BDBMessageStore.ENVIRONMENT_CONFIGURATION))).thenReturn(envMap); when(virtualHost.getAttribute(eq(MessageStoreConstants.OVERFULL_SIZE_ATTRIBUTE))).thenReturn(OVERFULL_SIZE); when(virtualHost.getAttribute(eq(MessageStoreConstants.UNDERFULL_SIZE_ATTRIBUTE))).thenReturn(UNDERFULL_SIZE); diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/MessageStoreCreatorTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/MessageStoreCreatorTest.java index 730001d849..48fb180984 100644 --- a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/MessageStoreCreatorTest.java +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/MessageStoreCreatorTest.java @@ -22,20 +22,15 @@ package org.apache.qpid.server.store.berkeleydb; import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.store.MessageStoreCreator; -import org.apache.qpid.server.store.berkeleydb.BDBMessageStore; import org.apache.qpid.test.utils.QpidTestCase; public class MessageStoreCreatorTest extends QpidTestCase { - private static final String[] STORE_TYPES = {BDBMessageStore.TYPE}; - public void testMessageStoreCreator() { MessageStoreCreator messageStoreCreator = new MessageStoreCreator(); - for (String type : STORE_TYPES) - { - MessageStore store = messageStoreCreator.createMessageStore(type); - assertNotNull("Store of type " + type + " is not created", store); - } + String type = new BDBMessageStoreFactory().getType(); + MessageStore store = messageStoreCreator.createMessageStore(type); + assertNotNull("Store of type " + type + " is not created", store); } } diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/StandardEnvironmentFacadeTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/StandardEnvironmentFacadeTest.java new file mode 100644 index 0000000000..b19e18b204 --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/StandardEnvironmentFacadeTest.java @@ -0,0 +1,128 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb; + +import java.io.File; +import java.util.Collections; + +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.util.FileUtils; + +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.Environment; + +public class StandardEnvironmentFacadeTest extends QpidTestCase +{ + protected File _storePath; + protected EnvironmentFacade _environmentFacade; + + protected void setUp() throws Exception + { + super.setUp(); + _storePath = new File(TMP_FOLDER + File.separator + "bdb" + File.separator + getTestName()); + } + + protected void tearDown() throws Exception + { + try + { + super.tearDown(); + if (_environmentFacade != null) + { + _environmentFacade.close(); + } + } + finally + { + if (_storePath != null) + { + FileUtils.delete(_storePath, true); + } + } + } + + public void testEnvironmentFacade() throws Exception + { + EnvironmentFacade ef = getEnvironmentFacade(); + assertNotNull("Environment should not be null", ef); + Environment e = ef.getEnvironment(); + assertTrue("Environment is not valid", e.isValid()); + } + + public void testClose() throws Exception + { + EnvironmentFacade ef = getEnvironmentFacade(); + ef.close(); + Environment e = ef.getEnvironment(); + + assertNull("Environment should be null after facade close", e); + } + + public void testOpenDatabases() throws Exception + { + EnvironmentFacade ef = getEnvironmentFacade(); + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + ef.openDatabases(dbConfig, "test1", "test2"); + Database test1 = ef.getOpenDatabase("test1"); + Database test2 = ef.getOpenDatabase("test2"); + + assertEquals("Unexpected name for open database test1", "test1" , test1.getDatabaseName()); + assertEquals("Unexpected name for open database test2", "test2" , test2.getDatabaseName()); + } + + public void testGetOpenDatabaseForNonExistingDatabase() throws Exception + { + EnvironmentFacade ef = getEnvironmentFacade(); + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + ef.openDatabases(dbConfig, "test1"); + Database test1 = ef.getOpenDatabase("test1"); + assertEquals("Unexpected name for open database test1", "test1" , test1.getDatabaseName()); + try + { + ef.getOpenDatabase("test2"); + fail("An exception should be thrown for the non existing database"); + } + catch(IllegalArgumentException e) + { + assertEquals("Unexpected exception message", "Database with name 'test2' has not been opened", e.getMessage()); + } + } + + EnvironmentFacade getEnvironmentFacade() throws Exception + { + if (_environmentFacade == null) + { + _environmentFacade = createEnvironmentFacade(); + } + return _environmentFacade; + } + + EnvironmentFacade createEnvironmentFacade() + { + return new StandardEnvironmentFacade(_storePath.getAbsolutePath(), Collections.<String, String>emptyMap()); + } + +} diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/VirtualHostTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/VirtualHostTest.java new file mode 100644 index 0000000000..7269988042 --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/VirtualHostTest.java @@ -0,0 +1,285 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.configuration.startup.ReplicationNodeRecoverer; +import org.apache.qpid.server.configuration.startup.VirtualHostRecoverer; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.logging.SystemOutMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.berkeleydb.replication.ReplicatedEnvironmentFacade; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.StandardVirtualHostFactory; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; +import org.apache.qpid.util.FileUtils; + +import com.sleepycat.je.EnvironmentConfig; +import com.sleepycat.je.rep.ReplicatedEnvironment; +import com.sleepycat.je.rep.ReplicationConfig; + +public class VirtualHostTest extends QpidTestCase +{ + + private Broker _broker; + private StatisticsGatherer _statisticsGatherer; + private RecovererProvider _recovererProvider; + private File _configFile; + private File _bdbStorePath; + private VirtualHost _host; + private ConfigurationEntryStore _store; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + CurrentActor.set(new TestLogActor(new SystemOutMessageLogger())); + + _store = mock(ConfigurationEntryStore.class); + _broker = BrokerTestHelper.createBrokerMock(); + TaskExecutor taslExecutor = mock(TaskExecutor.class); + when(taslExecutor.isTaskExecutorThread()).thenReturn(true); + when(_broker.getTaskExecutor()).thenReturn(taslExecutor); + + + _recovererProvider = new RecovererProvider() + { + @Override + public ConfiguredObjectRecoverer<? extends ConfiguredObject> getRecoverer(String type) + { + if (type.equals(ReplicationNode.class.getSimpleName())) + { + return new ReplicationNodeRecoverer(); + } + throw new IllegalArgumentException("Not supported type: " + type); + } + }; + + _statisticsGatherer = mock(StatisticsGatherer.class); + + _bdbStorePath = new File(TMP_FOLDER, getTestName() + "." + System.currentTimeMillis()); + _bdbStorePath.deleteOnExit(); + } + + @Override + protected void tearDown() throws Exception + { + try + { + if (_host != null) + { + _host.setDesiredState(_host.getActualState(), State.STOPPED); + } + } + finally + { + if (_configFile != null) + { + _configFile.delete(); + } + if (_bdbStorePath != null) + { + FileUtils.delete(_bdbStorePath, true); + } + super.tearDown(); + CurrentActor.remove(); + } + } + + public void testCreateBdbHaVirtualHostFromConfigurationEntry() + { + String repStreamTimeout = "2 h"; + String nodeName = "node"; + String groupName = "group"; + String nodeHostPort = "localhost:" + findFreePort(); + String helperHostPort = nodeHostPort; + String durability = "NO_SYNC,SYNC,NONE"; + + UUID nodeId = UUID.randomUUID(); + Map<String, Object> nodeAttributes = new HashMap<String, Object>(); + nodeAttributes.put(ReplicationNode.NAME, nodeName); + nodeAttributes.put(ReplicationNode.GROUP_NAME, groupName); + nodeAttributes.put(ReplicationNode.HOST_PORT, nodeHostPort); + nodeAttributes.put(ReplicationNode.HELPER_HOST_PORT, helperHostPort); + nodeAttributes.put(ReplicationNode.DURABILITY, durability); + nodeAttributes.put(ReplicationNode.STORE_PATH, _bdbStorePath.getAbsolutePath()); + nodeAttributes.put(ReplicationNode.REPLICATION_PARAMETERS, + Collections.singletonMap(ReplicationConfig.REP_STREAM_TIMEOUT, repStreamTimeout)); + + ConfigurationEntry nodeEntry = new ConfigurationEntry(nodeId, ReplicationNode.class.getSimpleName(), + nodeAttributes, Collections.<UUID> emptySet(), _store); + when(_store.getEntry(nodeId)).thenReturn(nodeEntry); + + String hostName = getName(); + + Map<String, Object> virtualHostAttributes = new HashMap<String, Object>(); + virtualHostAttributes.put(VirtualHost.NAME, hostName); + virtualHostAttributes.put(VirtualHost.TYPE, BDBHAVirtualHostFactory.TYPE); + + _host = createHost(virtualHostAttributes, Collections.singleton(nodeId)); + _host.setDesiredState(State.INITIALISING, State.ACTIVE); + + assertEquals("Unexpected host name", hostName, _host.getName()); + assertEquals("Unexpected host type", BDBHAVirtualHostFactory.TYPE, _host.getType()); + assertEquals("Unexpected store type", ReplicatedEnvironmentFacade.TYPE, _host.getAttribute(VirtualHost.STORE_TYPE)); + + ReplicationNode localNode = _host.getChildren(ReplicationNode.class).iterator().next(); + + assertEquals(nodeName, localNode.getName()); + assertEquals(groupName, localNode.getAttribute(ReplicationNode.GROUP_NAME)); + assertEquals(nodeHostPort, localNode.getAttribute(ReplicationNode.HOST_PORT)); + assertEquals(helperHostPort, localNode.getAttribute(ReplicationNode.HELPER_HOST_PORT)); + assertEquals(durability, localNode.getAttribute(ReplicationNode.DURABILITY)); + assertEquals("Unexpected store path", _bdbStorePath.getAbsolutePath(), localNode.getAttribute(ReplicationNode.STORE_PATH)); + + BDBMessageStore messageStore = (BDBMessageStore) _host.getMessageStore(); + ReplicatedEnvironment environment = (ReplicatedEnvironment) messageStore.getEnvironmentFacade().getEnvironment(); + ReplicationConfig envConfig = environment.getRepConfig(); + assertEquals("Unexpected JE replication stream timeout", repStreamTimeout, envConfig.getConfigParam(ReplicationConfig.REP_STREAM_TIMEOUT)); + + } + + public void testCreateBdbVirtualHostFromConfigurationFile() + { + String hostName = getName(); + long logFileMax = 2000000; + _host = createHostFromConfiguration(hostName, logFileMax); + _host.setDesiredState(State.INITIALISING, State.ACTIVE); + assertEquals("Unexpected host name", hostName, _host.getName()); + assertEquals("Unexpected host type", StandardVirtualHostFactory.TYPE, _host.getType()); + assertEquals("Unexpected store type", new BDBMessageStoreFactory().getType(), _host.getAttribute(VirtualHost.STORE_TYPE)); + assertEquals("Unexpected store path", _bdbStorePath.getAbsolutePath(), _host.getAttribute(VirtualHost.STORE_PATH)); + + BDBMessageStore messageStore = (BDBMessageStore) _host.getMessageStore(); + EnvironmentConfig envConfig = messageStore.getEnvironmentFacade().getEnvironment().getConfig(); + assertEquals("Unexpected JE log file max", String.valueOf(logFileMax), envConfig.getConfigParam(EnvironmentConfig.LOG_FILE_MAX)); + + } + + public void testCreateBdbHaVirtualHostFromConfigurationFile() + { + String hostName = getName(); + + String repStreamTimeout = "2 h"; + String nodeName = "node"; + String groupName = "group"; + String nodeHostPort = "localhost:" + findFreePort(); + String helperHostPort = nodeHostPort; + String durability = "NO_SYNC,SYNC,NONE"; + _host = createHaHostFromConfiguration(hostName, groupName, nodeName, nodeHostPort, helperHostPort, durability, repStreamTimeout); + _host.setDesiredState(State.INITIALISING, State.ACTIVE); + assertEquals("Unexpected host name", hostName, _host.getName()); + assertEquals("Unexpected host type", BDBHAVirtualHostFactory.TYPE, _host.getType()); + assertEquals("Unexpected store type", ReplicatedEnvironmentFacade.TYPE, _host.getAttribute(VirtualHost.STORE_TYPE)); + assertEquals("Unexpected store path", _bdbStorePath.getAbsolutePath(), _host.getAttribute(VirtualHost.STORE_PATH)); + + ReplicationNode localNode = _host.getChildren(ReplicationNode.class).iterator().next(); + + assertEquals(nodeName, localNode.getName()); + assertEquals(groupName, localNode.getAttribute(ReplicationNode.GROUP_NAME)); + assertEquals(nodeHostPort, localNode.getAttribute(ReplicationNode.HOST_PORT)); + assertEquals(helperHostPort, localNode.getAttribute(ReplicationNode.HELPER_HOST_PORT)); + assertEquals(durability, localNode.getAttribute(ReplicationNode.DURABILITY)); + + BDBMessageStore messageStore = (BDBMessageStore) _host.getMessageStore(); + ReplicatedEnvironment environment = (ReplicatedEnvironment) messageStore.getEnvironmentFacade().getEnvironment(); + ReplicationConfig envConfig = environment.getRepConfig(); + assertEquals("Unexpected JE replication stream timeout", repStreamTimeout, envConfig.getConfigParam(ReplicationConfig.REP_STREAM_TIMEOUT)); + } + + private VirtualHost createHost(Map<String, Object> attributes, Set<UUID> children) + { + ConfigurationEntry entry = new ConfigurationEntry(UUID.randomUUID(), VirtualHost.class.getSimpleName(), attributes, + children, _store); + + return new VirtualHostRecoverer(_statisticsGatherer).create(_recovererProvider, entry, _broker); + } + + private VirtualHost createHost(Map<String, Object> attributes) + { + return createHost(attributes, Collections.<UUID> emptySet()); + } + + private VirtualHost createHostFromConfiguration(String hostName, long logFileMax) + { + String content = "<virtualhosts><virtualhost><name>" + hostName + "</name><" + hostName + ">" + + "<store><class>" + BDBMessageStore.class.getName() + "</class>" + + "<environment-path>" + _bdbStorePath.getAbsolutePath() + "</environment-path>" + + "<envConfig><name>" + EnvironmentConfig.LOG_FILE_MAX + "</name><value>" + logFileMax + "</value></envConfig>" + + "</store>" + + "</" + hostName + "></virtualhost></virtualhosts>"; + Map<String, Object> attributes = writeConfigAndGenerateAttributes(content); + return createHost(attributes); + } + + + private VirtualHost createHaHostFromConfiguration(String hostName, String groupName, String nodeName, String nodeHostPort, String helperHostPort, String durability, String repStreamTimeout) + { + String content = "<virtualhosts><virtualhost><name>" + hostName + "</name><" + hostName + ">" + + "<type>" + BDBHAVirtualHostFactory.TYPE + "</type>" + + "<store><class>" + BDBMessageStore.class.getName() + "</class>" + + "<environment-path>" + _bdbStorePath.getAbsolutePath() + "</environment-path>" + + "<highAvailability>" + + "<groupName>" + groupName + "</groupName>" + + "<nodeName>" + nodeName + "</nodeName>" + + "<nodeHostPort>" + nodeHostPort + "</nodeHostPort>" + + "<helperHostPort>" + helperHostPort + "</helperHostPort>" + + "<durability>" + durability.replaceAll(",", "\\\\,") + "</durability>" + + "</highAvailability>" + + "<repConfig><name>" + ReplicationConfig.REP_STREAM_TIMEOUT + "</name><value>" + repStreamTimeout + "</value></repConfig>" + + "</store>" + + "</" + hostName + "></virtualhost></virtualhosts>"; + Map<String, Object> attributes = writeConfigAndGenerateAttributes(content); + return createHost(attributes); + } + + private Map<String, Object> writeConfigAndGenerateAttributes(String content) + { + _configFile = TestFileUtils.createTempFile(this, ".virtualhost.xml", content); + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.CONFIG_PATH, _configFile.getAbsolutePath()); + return attributes; + } +} + +
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNodeTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNodeTest.java new file mode 100644 index 0000000000..fc0926d38e --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/LocalReplicationNodeTest.java @@ -0,0 +1,309 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +import com.sleepycat.je.rep.ReplicatedEnvironment; + +public class LocalReplicationNodeTest extends QpidTestCase +{ + + private static final Object INVALID_VALUE = new Object(); + private UUID _id; + private VirtualHost _virtualHost; + private TaskExecutor _taskExecutor; + private ReplicatedEnvironmentFacade _facade; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _taskExecutor = mock(TaskExecutor.class); + when(_taskExecutor.isTaskExecutorThread()).thenReturn(true); + _virtualHost = mock(VirtualHost.class); + _facade = mock(ReplicatedEnvironmentFacade.class); + } + + @Override + public void tearDown() throws Exception + { + super.tearDown(); + } + + public void testCreateLocalReplicationNodeWithoutDefaultParametersAndValidParameters() + { + Map<String, Object> attributes = createValidAttributes(); + + LocalReplicationNode node = new LocalReplicationNode(_id, attributes, _virtualHost, _taskExecutor); + + assertNodeAttributes(attributes, node); + + for (Map.Entry<String, Object> attributeEntry : LocalReplicationNode.DEFAULTS.entrySet()) + { + assertEquals("Unexpected attribute value for attribute name " + attributeEntry.getKey(), attributeEntry.getValue(), node.getAttribute(attributeEntry.getKey())); + } + } + + public void testCreateLocalReplicationNodeWithoutDefaultParametersAndMissedParameters() + { + Map<String, Object> attributes = createValidAttributes(); + + for (Map.Entry<String, Object> attributeEntry : attributes.entrySet()) + { + String name = attributeEntry.getKey(); + Map<String, Object> incompleteAttributes = new HashMap<String, Object>(attributes); + incompleteAttributes.remove(name); + try + { + new LocalReplicationNode(_id, incompleteAttributes, _virtualHost, _taskExecutor); + fail("Node creation should fails when attribute " + name + " is missed"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + } + + public void testCreateLocalReplicationNodeWithoutDefaultParametersAndInvalidParameters() + { + + Map<String, Object> attributes = createValidAttributes(); + + for (Map.Entry<String, Object> attributeEntry : attributes.entrySet()) + { + String name = attributeEntry.getKey(); + Object value = attributeEntry.getValue(); + if (!(value instanceof String)) + { + Map<String, Object> invalidAttributes = new HashMap<String, Object>(attributes); + invalidAttributes.put(name, INVALID_VALUE); + try + { + new LocalReplicationNode(_id, attributes, _virtualHost, _taskExecutor); + fail("Node creation should fails when attribute " + name + " is invalid"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + } + } + + public void testCreateLocalReplicationNodeWithOverriddenDefaultParameters() + { + Map<String, Object> attributes = createValidAttributes(); + attributes.put(ReplicationNode.DURABILITY, "SYNC,SYNC,NONE"); + attributes.put(ReplicationNode.COALESCING_SYNC, false); + attributes.put(ReplicationNode.DESIGNATED_PRIMARY, true); + + LocalReplicationNode node = new LocalReplicationNode(_id, attributes, _virtualHost, _taskExecutor); + + assertNodeAttributes(attributes, node); + } + + public void testGetValuesFromReplicatedEnvironmentFacade() + { + LocalReplicationNode node = new LocalReplicationNode(_id, createValidAttributes(), _virtualHost, _taskExecutor); + + assertNull("Unexpected role attribute", node.getAttribute(ReplicationNode.ROLE)); + assertNull("Unexpected join time attribute", node.getAttribute(ReplicationNode.JOIN_TIME)); + assertNull("Unexpected last transaction id", node.getAttribute(ReplicationNode.LAST_KNOWN_REPLICATION_TRANSACTION_ID)); + assertEquals("Unexpected priority attribute", LocalReplicationNode.DEFAULT_PRIORITY, node.getAttribute(ReplicationNode.PRIORITY)); + assertEquals("Unexpected quorum override attribute", LocalReplicationNode.DEFAULT_QUORUM_OVERRIDE, node.getAttribute(ReplicationNode.QUORUM_OVERRIDE)); + assertEquals("Unexpected designated primary attribute", LocalReplicationNode.DEFAULT_DESIGNATED_PRIMARY, node.getAttribute(ReplicationNode.DESIGNATED_PRIMARY)); + + String masterState = "MASTER"; + long joinTime = System.currentTimeMillis(); + long lastKnowTransactionId = 1000l; + boolean designatedPrimary = true; + int priority = 2; + int quorumOverride = 3; + + when(_facade.getNodeState()).thenReturn(masterState); + when(_facade.getJoinTime()).thenReturn(joinTime); + when(_facade.getLastKnownReplicationTransactionId()).thenReturn(lastKnowTransactionId); + when(_facade.isDesignatedPrimary()).thenReturn(designatedPrimary); + when(_facade.getPriority()).thenReturn(priority); + when(_facade.getElectableGroupSizeOverride()).thenReturn(quorumOverride); + + node.setReplicatedEnvironmentFacade(_facade); + assertEquals("Unexpected role attribute", masterState, node.getAttribute(ReplicationNode.ROLE)); + assertEquals("Unexpected join time attribute", joinTime, node.getAttribute(ReplicationNode.JOIN_TIME)); + assertEquals("Unexpected last transaction id attribute", lastKnowTransactionId, node.getAttribute(ReplicationNode.LAST_KNOWN_REPLICATION_TRANSACTION_ID)); + assertEquals("Unexpected priority attribute", priority, node.getAttribute(ReplicationNode.PRIORITY)); + assertEquals("Unexpected quorum override attribute", quorumOverride, node.getAttribute(ReplicationNode.QUORUM_OVERRIDE)); + } + + public void testSetDesignatedPrimary() throws Exception + { + LocalReplicationNode node = new LocalReplicationNode(_id, createValidAttributes(), _virtualHost, _taskExecutor); + node.setReplicatedEnvironmentFacade(_facade); + + node.setAttributes(Collections.<String, Object>singletonMap(ReplicationNode.DESIGNATED_PRIMARY, true)); + + verify(_facade).setDesignatedPrimary(true); + + node.setAttributes(Collections.<String, Object>singletonMap(ReplicationNode.DESIGNATED_PRIMARY, false)); + verify(_facade).setDesignatedPrimary(false); + } + + public void testSetPriority() throws Exception + { + LocalReplicationNode node = new LocalReplicationNode(_id, createValidAttributes(), _virtualHost, _taskExecutor); + node.setReplicatedEnvironmentFacade(_facade); + node.setAttributes(Collections.<String, Object>singletonMap(ReplicationNode.PRIORITY, 100)); + + verify(_facade).setPriority(100); + } + + public void testSetQuorumOverride() throws Exception + { + LocalReplicationNode node = new LocalReplicationNode(_id, createValidAttributes(), _virtualHost, _taskExecutor); + node.setReplicatedEnvironmentFacade(_facade); + + node.setAttributes(Collections.<String, Object>singletonMap(ReplicationNode.QUORUM_OVERRIDE, 10)); + + verify(_facade).setElectableGroupSizeOverride(10); + } + + public void testSetRole() throws Exception + { + when(_facade.getNodeState()).thenReturn(ReplicatedEnvironment.State.REPLICA.name()); + + LocalReplicationNode node = new LocalReplicationNode(_id, createValidAttributes(), _virtualHost, _taskExecutor); + node.setReplicatedEnvironmentFacade(_facade); + + node.setAttributes(Collections.<String, Object>singletonMap(ReplicationNode.ROLE, ReplicatedEnvironment.State.MASTER.name())); + + verify(_facade).transferMasterToSelfAsynchronously(); + } + + public void testSetRoleToReplicaUnsupported() throws Exception + { + when(_facade.getNodeState()).thenReturn(ReplicatedEnvironment.State.REPLICA.name()); + + LocalReplicationNode node = new LocalReplicationNode(_id, createValidAttributes(), _virtualHost, _taskExecutor); + node.setReplicatedEnvironmentFacade(_facade); + + try + { + node.setAttributes(Collections.<String, Object>singletonMap(ReplicationNode.ROLE, ReplicatedEnvironment.State.REPLICA.name())); + fail("Exception not thrown"); + } + catch(IllegalConfigurationException e) + { + // PASS + } + } + + public void testSetRoleWhenCurrentRoleNotRepliaIsUnsupported() throws Exception + { + when(_facade.getNodeState()).thenReturn(ReplicatedEnvironment.State.MASTER.name()); + + LocalReplicationNode node = new LocalReplicationNode(_id, createValidAttributes(), _virtualHost, _taskExecutor); + node.setReplicatedEnvironmentFacade(_facade); + + try + { + node.setAttributes(Collections.<String, Object>singletonMap(ReplicationNode.ROLE, ReplicatedEnvironment.State.MASTER.name())); + fail("Exception not thrown"); + } + catch(IllegalConfigurationException e) + { + // PASS + } + } + + public void testSetImmutableAttributesThrowException() throws Exception + { + Map<String, Object> changeAttributeMap = new HashMap<String, Object>(); + changeAttributeMap.put(ReplicationNode.GROUP_NAME, "newGroupName"); + changeAttributeMap.put(ReplicationNode.HELPER_HOST_PORT, "newhost:1234"); + changeAttributeMap.put(ReplicationNode.HOST_PORT, "newhost:1234"); + changeAttributeMap.put(ReplicationNode.COALESCING_SYNC, Boolean.FALSE); + changeAttributeMap.put(ReplicationNode.DURABILITY, "durability"); + changeAttributeMap.put(ReplicationNode.JOIN_TIME, 1000l); + changeAttributeMap.put(ReplicationNode.LAST_KNOWN_REPLICATION_TRANSACTION_ID, 10001l); + changeAttributeMap.put(ReplicationNode.NAME, "newName"); + changeAttributeMap.put(ReplicationNode.STORE_PATH, "/not/used"); + changeAttributeMap.put(ReplicationNode.PARAMETERS, Collections.emptyMap()); + changeAttributeMap.put(ReplicationNode.REPLICATION_PARAMETERS, Collections.emptyMap()); + + for (Entry<String, Object> entry : changeAttributeMap.entrySet()) + { + assertSetAttributesThrowsException(entry.getKey(), entry.getValue()); + } + } + + private void assertSetAttributesThrowsException(String attributeName, Object attributeValue) + { + LocalReplicationNode node = new LocalReplicationNode(_id, createValidAttributes(), _virtualHost, _taskExecutor); + + try + { + node.setAttributes(Collections.<String, Object>singletonMap(attributeName, attributeValue)); + fail("Operation to change attribute '" + attributeName + "' should fail"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + + private Map<String, Object> createValidAttributes() + { + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(ReplicationNode.NAME, "testNode"); + attributes.put(ReplicationNode.GROUP_NAME, "testGroup"); + attributes.put(ReplicationNode.HOST_PORT, "localhost:5000"); + attributes.put(ReplicationNode.HELPER_HOST_PORT, "localhost:5001"); + attributes.put(ReplicationNode.STORE_PATH, TMP_FOLDER + File.separator + getTestName()); + return attributes; + } + + private void assertNodeAttributes(Map<String, Object> expectedAttributes, + LocalReplicationNode node) + { + for (Map.Entry<String, Object> attributeEntry : expectedAttributes.entrySet()) + { + assertEquals("Unexpected attribute value for attribute name " + attributeEntry.getKey(), attributeEntry.getValue(), node.getAttribute(attributeEntry.getKey())); + } + } + +} diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/NoopReplicationGroupListener.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/NoopReplicationGroupListener.java new file mode 100644 index 0000000000..21c902ae8f --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/NoopReplicationGroupListener.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.replication.ReplicationGroupListener; + +class NoopReplicationGroupListener implements ReplicationGroupListener +{ + @Override + public void onReplicationNodeRecovered(ReplicationNode node) + { + } + + @Override + public void onReplicationNodeAddedToGroup(ReplicationNode node) + { + } + + @Override + public void onReplicationNodeRemovedFromGroup(ReplicationNode node) + { + } +}
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/RemoteReplicationNodeTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/RemoteReplicationNodeTest.java new file mode 100644 index 0000000000..25c58e47a5 --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/RemoteReplicationNodeTest.java @@ -0,0 +1,176 @@ +package org.apache.qpid.server.store.berkeleydb.replication; + +import static org.apache.qpid.server.model.ReplicationNode.COALESCING_SYNC; +import static org.apache.qpid.server.model.ReplicationNode.DURABILITY; +import static org.apache.qpid.server.model.ReplicationNode.GROUP_NAME; +import static org.apache.qpid.server.model.ReplicationNode.HELPER_HOST_PORT; +import static org.apache.qpid.server.model.ReplicationNode.HOST_PORT; +import static org.apache.qpid.server.model.ReplicationNode.JOIN_TIME; +import static org.apache.qpid.server.model.ReplicationNode.LAST_KNOWN_REPLICATION_TRANSACTION_ID; +import static org.apache.qpid.server.model.ReplicationNode.NAME; +import static org.apache.qpid.server.model.ReplicationNode.PARAMETERS; +import static org.apache.qpid.server.model.ReplicationNode.REPLICATION_PARAMETERS; +import static org.apache.qpid.server.model.ReplicationNode.ROLE; +import static org.apache.qpid.server.model.ReplicationNode.STORE_PATH; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +import com.sleepycat.je.rep.MasterStateException; +import com.sleepycat.je.rep.NodeState; +import com.sleepycat.je.rep.ReplicatedEnvironment.State; +import com.sleepycat.je.rep.ReplicationNode; + +public class RemoteReplicationNodeTest extends QpidTestCase +{ + + private RemoteReplicationNode _node; + private String _groupName; + private VirtualHost _virtualHost; + private TaskExecutor _taskExecutor; + private ReplicationNode _replicationNode; + private String _nodeName; + private int _port; + private ReplicatedEnvironmentFacade _replicatedEnvironmentFacade; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _groupName = getTestName(); + _nodeName = getTestName() + "Name"; + _port = 5000; + _replicationNode = mock(ReplicationNode.class); + _virtualHost = mock(VirtualHost.class); + _taskExecutor = mock(TaskExecutor.class); + _replicatedEnvironmentFacade = mock(ReplicatedEnvironmentFacade.class); + when(_replicatedEnvironmentFacade.getGroupName()).thenReturn(_groupName); + + when(_taskExecutor.isTaskExecutorThread()).thenReturn(true); + when(_replicationNode.getName()).thenReturn(_nodeName); + when(_replicationNode.getHostName()).thenReturn("localhost"); + when(_replicationNode.getPort()).thenReturn(_port); + + _node = new RemoteReplicationNode(_replicationNode, _virtualHost, _taskExecutor, _replicatedEnvironmentFacade); + } + + public void testGetAttribute() throws Exception + { + State state = State.MASTER; + long joinTime = System.currentTimeMillis(); + long currentTxnEndVLSN = 3; + + updateNodeState(state, joinTime, currentTxnEndVLSN); + + assertEquals("Unexpected name", _nodeName, _node.getAttribute(NAME)); + assertEquals("Unexpected group name", _groupName, _node.getAttribute(GROUP_NAME)); + assertEquals("Unexpected state", state.name(), _node.getAttribute(ROLE)); + assertEquals("Unexpected transaction id", currentTxnEndVLSN, _node.getAttribute(LAST_KNOWN_REPLICATION_TRANSACTION_ID)); + assertEquals("Unexpected join time", joinTime, _node.getAttribute(JOIN_TIME)); + } + + public void testSetRoleAttribute() throws Exception + { + updateNodeState(); + _node.setAttributes(Collections.<String, Object>singletonMap(ROLE, State.MASTER.name())); + + verify(_replicatedEnvironmentFacade).transferMasterAsynchronously(_nodeName); + } + + public void testSetRoleAttributeDisallowedIfAlreadyMaster() throws Exception + { + updateNodeState(State.MASTER, System.currentTimeMillis(), 0L); + try + { + _node.setAttributes(Collections.<String, Object>singletonMap(ROLE, State.MASTER.name())); + fail("Exception not thrown"); + } + catch (IllegalConfigurationException ice) + { + // pass + } + + verify(_replicatedEnvironmentFacade, never()).transferMasterAsynchronously(_nodeName); + } + + public void testSetDesiredStateToDeleted() throws Exception + { + _node.setDesiredState(_node.getActualState(), org.apache.qpid.server.model.State.DELETED); + verify(_replicatedEnvironmentFacade).removeNodeFromGroup(_nodeName); + } + + public void testSetDesiredStateToDeletedOnMasterStateException() throws Exception + { + doThrow(new MasterStateException("mocked exception")).when(_replicatedEnvironmentFacade).removeNodeFromGroup(_nodeName); + try + { + _node.setDesiredState(_node.getActualState(), org.apache.qpid.server.model.State.DELETED); + fail("Exception not thrown"); + } + catch(IllegalStateTransitionException e) + { + // pass + } + } + + public void testSetImmutableAttributesThrowException() throws Exception + { + Map<String, Object> changeAttributeMap = new HashMap<String, Object>(); + changeAttributeMap.put(GROUP_NAME, "newGroupName"); + changeAttributeMap.put(HELPER_HOST_PORT, "newhost:1234"); + changeAttributeMap.put(HOST_PORT, "newhost:1234"); + changeAttributeMap.put(COALESCING_SYNC, Boolean.FALSE); + changeAttributeMap.put(DURABILITY, "durability"); + changeAttributeMap.put(JOIN_TIME, 1000l); + changeAttributeMap.put(LAST_KNOWN_REPLICATION_TRANSACTION_ID, 10001l); + changeAttributeMap.put(NAME, "newName"); + changeAttributeMap.put(STORE_PATH, "/not/used"); + changeAttributeMap.put(PARAMETERS, Collections.emptyMap()); + changeAttributeMap.put(REPLICATION_PARAMETERS, Collections.emptyMap()); + + for (Entry<String, Object> entry : changeAttributeMap.entrySet()) + { + assertSetAttributesThrowsException(entry.getKey(), entry.getValue()); + } + } + + private void assertSetAttributesThrowsException(String attributeName, Object attributeValue) throws Exception + { + updateNodeState(); + + try + { + _node.setAttributes(Collections.<String, Object>singletonMap(attributeName, attributeValue)); + fail("Operation to change attribute '" + attributeName + "' should fail"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + + private void updateNodeState() throws Exception + { + updateNodeState( State.REPLICA, System.currentTimeMillis(), 3); + } + + private void updateNodeState(State state, long joinTime, long currentTxnEndVLSN) throws Exception + { + NodeState nodeState = new NodeState(_nodeName, _groupName, state, null, null, joinTime, currentTxnEndVLSN, 2, 1, 0, null, 0.0); + when(_replicatedEnvironmentFacade.getRemoteNodeState(_replicationNode)).thenReturn(nodeState); + _node.updateNodeState(); + } +} diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacadeTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacadeTest.java new file mode 100644 index 0000000000..052341c810 --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/ReplicatedEnvironmentFacadeTest.java @@ -0,0 +1,673 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import static org.apache.qpid.server.model.ReplicationNode.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.replication.ReplicationGroupListener; +import org.apache.qpid.server.store.berkeleydb.EnvironmentFacade; +import org.apache.qpid.server.store.berkeleydb.replication.RemoteReplicationNode; +import org.apache.qpid.server.store.berkeleydb.replication.RemoteReplicationNodeFactory; +import org.apache.qpid.server.store.berkeleydb.replication.ReplicatedEnvironmentFacade; +import org.apache.qpid.server.store.berkeleydb.replication.ReplicatedEnvironmentFacadeFactory; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; +import org.apache.qpid.util.FileUtils; + +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.Durability; +import com.sleepycat.je.Environment; +import com.sleepycat.je.rep.InsufficientReplicasException; +import com.sleepycat.je.rep.ReplicatedEnvironment.State; +import com.sleepycat.je.rep.ReplicationConfig; +import com.sleepycat.je.rep.StateChangeEvent; +import com.sleepycat.je.rep.StateChangeListener; + +public class ReplicatedEnvironmentFacadeTest extends QpidTestCase +{ + private static final Logger LOGGER = Logger.getLogger(ReplicatedEnvironmentFacadeTest.class); + + private static final int TEST_NODE_PORT = new QpidTestCase().findFreePort(); + private static final int LISTENER_TIMEOUT = 5; + private static final int WAIT_STATE_CHANGE_TIMEOUT = 30; + private static final String TEST_GROUP_NAME = "testGroupName"; + private static final String TEST_NODE_NAME = "testNodeName"; + private static final String TEST_NODE_HOST_PORT = "localhost:" + TEST_NODE_PORT; + private static final String TEST_NODE_HELPER_HOST_PORT = TEST_NODE_HOST_PORT; + private static final String TEST_DURABILITY = Durability.parse("NO_SYNC,NO_SYNC,SIMPLE_MAJORITY").toString(); + private static final boolean TEST_DESIGNATED_PRIMARY = false; + private static final boolean TEST_COALESCING_SYNC = true; + private static final int TEST_PRIORITY = 10; + private static final int TEST_ELECTABLE_GROUP_OVERRIDE = 0; + + private File _storePath; + private final Map<String, ReplicatedEnvironmentFacade> _nodes = new HashMap<String, ReplicatedEnvironmentFacade>(); + private VirtualHost _virtualHost = mock(VirtualHost.class); + + private RemoteReplicationNodeFactory _remoteReplicationNodeFactory = new ReplicatedEnvironmentFacadeFactory.RemoteReplicationNodeFactoryImpl(_virtualHost); + + public void setUp() throws Exception + { + super.setUp(); + + TaskExecutor taskExecutor = mock(TaskExecutor.class); + when(taskExecutor.isTaskExecutorThread()).thenReturn(true); + when(_virtualHost.getTaskExecutor()).thenReturn(taskExecutor); + + _storePath = TestFileUtils.createTestDirectory("bdb", true); + + when(_virtualHost.getAttribute(VirtualHost.REMOTE_REPLICATION_NODE_MONITOR_INTERVAL)).thenReturn(100L); + setTestSystemProperty(ReplicatedEnvironmentFacade.DB_PING_SOCKET_TIMEOUT_PROPERTY_NAME, "100"); + } + + @Override + public void tearDown() throws Exception + { + try + { + for (EnvironmentFacade ef : _nodes.values()) + { + ef.close(); + } + } + finally + { + try + { + if (_storePath != null) + { + FileUtils.delete(_storePath, true); + } + } + finally + { + super.tearDown(); + } + } + } + public void testEnvironmentFacade() throws Exception + { + EnvironmentFacade ef = createMaster(); + assertNotNull("Environment should not be null", ef); + Environment e = ef.getEnvironment(); + assertTrue("Environment is not valid", e.isValid()); + } + + public void testClose() throws Exception + { + EnvironmentFacade ef = createMaster(); + ef.close(); + Environment e = ef.getEnvironment(); + + assertNull("Environment should be null after facade close", e); + } + + public void testOpenDatabases() throws Exception + { + EnvironmentFacade ef = createMaster(); + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + ef.openDatabases(dbConfig, "test1", "test2"); + Database test1 = ef.getOpenDatabase("test1"); + Database test2 = ef.getOpenDatabase("test2"); + + assertEquals("Unexpected name for open database test1", "test1" , test1.getDatabaseName()); + assertEquals("Unexpected name for open database test2", "test2" , test2.getDatabaseName()); + } + + public void testGetOpenDatabaseForNonExistingDatabase() throws Exception + { + EnvironmentFacade ef = createMaster(); + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + ef.openDatabases(dbConfig, "test1"); + Database test1 = ef.getOpenDatabase("test1"); + assertEquals("Unexpected name for open database test1", "test1" , test1.getDatabaseName()); + try + { + ef.getOpenDatabase("test2"); + fail("An exception should be thrown for the non existing database"); + } + catch(IllegalArgumentException e) + { + assertEquals("Unexpected exception message", "Database with name 'test2' has never been requested to be opened", e.getMessage()); + } + } + + public void testGetGroupName() throws Exception + { + assertEquals("Unexpected group name", TEST_GROUP_NAME, createMaster().getGroupName()); + } + + public void testGetNodeName() throws Exception + { + assertEquals("Unexpected group name", TEST_NODE_NAME, createMaster().getNodeName()); + } + + public void testLastKnownReplicationTransactionId() throws Exception + { + ReplicatedEnvironmentFacade master = createMaster(); + long lastKnownReplicationTransactionId = master.getLastKnownReplicationTransactionId(); + assertTrue("Unexpected LastKnownReplicationTransactionId " + lastKnownReplicationTransactionId, lastKnownReplicationTransactionId > 0); + } + + public void testGetNodeHostPort() throws Exception + { + assertEquals("Unexpected node host port", TEST_NODE_HOST_PORT, createMaster().getHostPort()); + } + + public void testGetHelperHostPort() throws Exception + { + assertEquals("Unexpected node helper host port", TEST_NODE_HELPER_HOST_PORT, createMaster().getHelperHostPort()); + } + + public void testGetDurability() throws Exception + { + assertEquals("Unexpected durability", TEST_DURABILITY.toString(), createMaster().getDurability()); + } + + public void testIsCoalescingSync() throws Exception + { + assertEquals("Unexpected coalescing sync", TEST_COALESCING_SYNC, createMaster().isCoalescingSync()); + } + + public void testGetNodeState() throws Exception + { + assertEquals("Unexpected state", State.MASTER.name(), createMaster().getNodeState()); + } + + + public void testPriority() throws Exception + { + ReplicatedEnvironmentFacade facade = createMaster(); + assertEquals("Unexpected priority", TEST_PRIORITY, facade.getPriority()); + Future<Void> future = facade.setPriority(TEST_PRIORITY + 1); + future.get(5, TimeUnit.SECONDS); + assertEquals("Unexpected priority after change", TEST_PRIORITY + 1, facade.getPriority()); + } + + public void testDesignatedPrimary() throws Exception + { + ReplicatedEnvironmentFacade master = createMaster(); + assertEquals("Unexpected designated primary", TEST_DESIGNATED_PRIMARY, master.isDesignatedPrimary()); + Future<Void> future = master.setDesignatedPrimary(!TEST_DESIGNATED_PRIMARY); + future.get(5, TimeUnit.SECONDS); + assertEquals("Unexpected designated primary after change", !TEST_DESIGNATED_PRIMARY, master.isDesignatedPrimary()); + } + + public void testElectableGroupSizeOverride() throws Exception + { + ReplicatedEnvironmentFacade facade = createMaster(); + assertEquals("Unexpected Electable Group Size Override", TEST_ELECTABLE_GROUP_OVERRIDE, facade.getElectableGroupSizeOverride()); + Future<Void> future = facade.setElectableGroupSizeOverride(TEST_ELECTABLE_GROUP_OVERRIDE + 1); + future.get(5, TimeUnit.SECONDS); + assertEquals("Unexpected Electable Group Size Override after change", TEST_ELECTABLE_GROUP_OVERRIDE + 1, facade.getElectableGroupSizeOverride()); + } + + public void testReplicationGroupListenerHearsAboutExistingRemoteReplicationNodes() throws Exception + { + ReplicatedEnvironmentFacade master = createMaster(); + String nodeName2 = TEST_NODE_NAME + "_2"; + String host = "localhost"; + int port = getNextAvailable(TEST_NODE_PORT + 1); + String node2NodeHostPort = host + ":" + port; + + final AtomicInteger invocationCount = new AtomicInteger(); + final CountDownLatch nodeRecoveryLatch = new CountDownLatch(1); + ReplicationGroupListener listener = new NoopReplicationGroupListener() + { + @Override + public void onReplicationNodeRecovered(ReplicationNode node) + { + nodeRecoveryLatch.countDown(); + invocationCount.incrementAndGet(); + } + }; + + addReplica(nodeName2, node2NodeHostPort, listener); + + assertEquals("Unexpected number of nodes", 2, master.getNumberOfElectableGroupMembers()); + + assertTrue("Listener not fired within timeout", nodeRecoveryLatch.await(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + assertEquals("Unexpected number of listener invocations", 1, invocationCount.get()); + } + + public void testReplicationGroupListenerHearsNodeAdded() throws Exception + { + final CountDownLatch nodeAddedLatch = new CountDownLatch(1); + final AtomicInteger invocationCount = new AtomicInteger(); + ReplicationGroupListener listener = new NoopReplicationGroupListener() + { + @Override + public void onReplicationNodeAddedToGroup(ReplicationNode node) + { + invocationCount.getAndIncrement(); + nodeAddedLatch.countDown(); + } + }; + + TestStateChangeListener stateChangeListener = new TestStateChangeListener(State.MASTER); + ReplicatedEnvironmentFacade replicatedEnvironmentFacade = addNode(State.MASTER, stateChangeListener, listener); + assertTrue("Master was not started", stateChangeListener.awaitForStateChange(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + + assertEquals("Unexpected number of nodes at start of test", 1, replicatedEnvironmentFacade.getNumberOfElectableGroupMembers()); + + String node2Name = TEST_NODE_NAME + "_2"; + String node2NodeHostPort = "localhost" + ":" + getNextAvailable(TEST_NODE_PORT + 1); + addReplica(node2Name, node2NodeHostPort); + + assertTrue("Listener not fired within timeout", nodeAddedLatch.await(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + + assertEquals("Unexpected number of nodes", 2, replicatedEnvironmentFacade.getNumberOfElectableGroupMembers()); + + assertEquals("Unexpected number of listener invocations", 1, invocationCount.get()); + } + + public void testReplicationGroupListenerHearsNodeRemoved() throws Exception + { + final CountDownLatch nodeDeletedLatch = new CountDownLatch(1); + final CountDownLatch nodeAddedLatch = new CountDownLatch(1); + final AtomicInteger invocationCount = new AtomicInteger(); + ReplicationGroupListener listener = new NoopReplicationGroupListener() + { + @Override + public void onReplicationNodeRecovered(ReplicationNode node) + { + nodeAddedLatch.countDown(); + } + + @Override + public void onReplicationNodeAddedToGroup(ReplicationNode node) + { + nodeAddedLatch.countDown(); + } + + @Override + public void onReplicationNodeRemovedFromGroup(ReplicationNode node) + { + invocationCount.getAndIncrement(); + nodeDeletedLatch.countDown(); + } + }; + + TestStateChangeListener stateChangeListener = new TestStateChangeListener(State.MASTER); + ReplicatedEnvironmentFacade replicatedEnvironmentFacade = addNode(State.MASTER, stateChangeListener, listener); + assertTrue("Master was not started", stateChangeListener.awaitForStateChange(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + + String node2Name = TEST_NODE_NAME + "_2"; + String node2NodeHostPort = "localhost" + ":" + getNextAvailable(TEST_NODE_PORT + 1); + addReplica(node2Name, node2NodeHostPort); + + assertEquals("Unexpected number of nodes at start of test", 2, replicatedEnvironmentFacade.getNumberOfElectableGroupMembers()); + + // Need to await the listener hearing the addition of the node to the model. + assertTrue("Node add not fired within timeout", nodeAddedLatch.await(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + + // Now remove the node and ensure we hear the event + replicatedEnvironmentFacade.removeNodeFromGroup(node2Name); + + assertTrue("Node delete not fired within timeout", nodeDeletedLatch.await(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + + assertEquals("Unexpected number of nodes after node removal", 1, replicatedEnvironmentFacade.getNumberOfElectableGroupMembers()); + + assertEquals("Unexpected number of listener invocations", 1, invocationCount.get()); + } + + public void testMasterHearsRemoteNodeRoles() throws Exception + { + + final CountDownLatch nodeAddedLatch = new CountDownLatch(1); + final AtomicReference<ReplicationNode> nodeRef = new AtomicReference<ReplicationNode>(); + ReplicationGroupListener listener = new NoopReplicationGroupListener() + { + @Override + public void onReplicationNodeAddedToGroup(ReplicationNode node) + { + nodeRef.set(node); + nodeAddedLatch.countDown(); + } + }; + + TestStateChangeListener stateChangeListener = new TestStateChangeListener(State.MASTER); + ReplicatedEnvironmentFacade replicatedEnvironmentFacade = addNode(State.MASTER, stateChangeListener, listener); + assertTrue("Master was not started", stateChangeListener.awaitForStateChange(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + + String node2Name = TEST_NODE_NAME + "_2"; + String node2NodeHostPort = "localhost" + ":" + getNextAvailable(TEST_NODE_PORT + 1); + addReplica(node2Name, node2NodeHostPort); + + assertEquals("Unexpected number of nodes at start of test", 2, replicatedEnvironmentFacade.getNumberOfElectableGroupMembers()); + + assertTrue("Node add not fired within timeout", nodeAddedLatch.await(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + + RemoteReplicationNode remoteNode = (RemoteReplicationNode)nodeRef.get(); + assertEquals("Unexpcted node name", node2Name, remoteNode.getName()); + + // Need to poll to await the remote node updating itself + long timeout = System.currentTimeMillis() + 5000; + while(!State.REPLICA.name().equals(remoteNode.getAttribute(ReplicationNode.ROLE)) && System.currentTimeMillis() < timeout) + { + Thread.sleep(200); + } + + assertEquals("Unexpcted node role (after waiting)", State.REPLICA.name(), remoteNode.getAttribute(ReplicationNode.ROLE)); + assertNotNull("Replica node " + ReplicationNode.JOIN_TIME + " attribute is not set", remoteNode.getAttribute(ReplicationNode.JOIN_TIME)); + assertNotNull("Replica node " + ReplicationNode.LAST_KNOWN_REPLICATION_TRANSACTION_ID + " attribute is not set", remoteNode.getAttribute(ReplicationNode.LAST_KNOWN_REPLICATION_TRANSACTION_ID)); + } + + public void testRemoveNodeFromGroup() throws Exception + { + ReplicatedEnvironmentFacade environmentFacade = createMaster(); + + String node2Name = TEST_NODE_NAME + "_2"; + String node2NodeHostPort = "localhost:" + getNextAvailable(TEST_NODE_PORT + 1); + ReplicatedEnvironmentFacade ref2 = addReplica(node2Name, node2NodeHostPort); + + assertEquals("Unexpected group members count", 2, environmentFacade.getNumberOfElectableGroupMembers()); + ref2.close(); + + environmentFacade.removeNodeFromGroup(node2Name); + assertEquals("Unexpected group members count", 1, environmentFacade.getNumberOfElectableGroupMembers()); + } + + public void testEnvironmentRestartOnInsufficientReplicas() throws Exception + { + long startTime = System.currentTimeMillis(); + + ReplicatedEnvironmentFacade master = createMaster(); + + int replica1Port = getNextAvailable(TEST_NODE_PORT + 1); + String replica1NodeName = TEST_NODE_NAME + "_1"; + String replica1NodeHostPort = "localhost:" + replica1Port; + ReplicatedEnvironmentFacade replica1 = addReplica(replica1NodeName, replica1NodeHostPort); + + int replica2Port = getNextAvailable(replica1Port + 1); + String replica2NodeName = TEST_NODE_NAME + "_2"; + String replica2NodeHostPort = "localhost:" + replica2Port; + ReplicatedEnvironmentFacade replica2 = addReplica(replica2NodeName, replica2NodeHostPort); + + long setUpTime = System.currentTimeMillis(); + LOGGER.debug("XXX Start Up Time " + (setUpTime - startTime)); + String databaseName = "test"; + + DatabaseConfig dbConfig = createDatabase(master, databaseName); + + // close replicas + replica1.close(); + replica2.close(); + + long closeTime = System.currentTimeMillis(); + LOGGER.debug("XXX Env close Time " + (closeTime - setUpTime)); + Environment e = master.getEnvironment(); + Database db = master.getOpenDatabase(databaseName); + try + { + master.openDatabases(dbConfig, "test2"); + fail("Opening of new database without quorum should fail"); + } + catch(InsufficientReplicasException ex) + { + master.handleDatabaseException(null, ex); + } + long openDatabaseTime = System.currentTimeMillis(); + LOGGER.debug("XXX Open db Time " + (openDatabaseTime - closeTime )); + + replica1 = addReplica(replica1NodeName, replica1NodeHostPort); + replica2 = addReplica(replica2NodeName, replica2NodeHostPort); + + long reopenTime = System.currentTimeMillis(); + LOGGER.debug("XXX Restart Time " + (reopenTime - openDatabaseTime )); + // Need to poll to await the remote node updating itself + long timeout = System.currentTimeMillis() + 5000; + while(!(State.REPLICA.name().equals(master.getNodeState()) || State.MASTER.name().equals(master.getNodeState()) ) && System.currentTimeMillis() < timeout) + { + Thread.sleep(200); + } + long recoverTime = System.currentTimeMillis(); + LOGGER.debug("XXX Recover Time " + (recoverTime - reopenTime)); + assertTrue("The node could not rejoin the cluster. State is " + master.getNodeState(), + State.REPLICA.name().equals(master.getNodeState()) || State.MASTER.name().equals(master.getNodeState()) ); + + Environment e2 = master.getEnvironment(); + assertNotSame("Environment has not been restarted", e2, e); + + Database db1 = master.getOpenDatabase(databaseName); + assertNotSame("Database should be the re-created", db1, db); + } + + public void testEnvironmentAutomaticallyRestartsAndBecomesUnknownOnInsufficientReplicas() throws Exception + { + final CountDownLatch masterLatch = new CountDownLatch(1); + final AtomicInteger masterStateChangeCount = new AtomicInteger(); + final CountDownLatch unknownLatch = new CountDownLatch(1); + final AtomicInteger unknownStateChangeCount = new AtomicInteger(); + StateChangeListener stateChangeListener = new StateChangeListener() + { + @Override + public void stateChange(StateChangeEvent stateChangeEvent) throws RuntimeException + { + if (stateChangeEvent.getState() == State.MASTER) + { + masterStateChangeCount.incrementAndGet(); + masterLatch.countDown(); + } + else if (stateChangeEvent.getState() == State.UNKNOWN) + { + unknownStateChangeCount.incrementAndGet(); + unknownLatch.countDown(); + } + } + }; + + addNode(State.MASTER, stateChangeListener, new NoopReplicationGroupListener()); + assertTrue("Master was not started", masterLatch.await(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + + int replica1Port = getNextAvailable(TEST_NODE_PORT + 1); + String node1NodeHostPort = "localhost:" + replica1Port; + int replica2Port = getNextAvailable(replica1Port + 1); + String node2NodeHostPort = "localhost:" + replica2Port; + + ReplicatedEnvironmentFacade replica1 = addReplica(TEST_NODE_NAME + "_1", node1NodeHostPort); + ReplicatedEnvironmentFacade replica2 = addReplica(TEST_NODE_NAME + "_2", node2NodeHostPort); + + // close replicas + replica1.close(); + replica2.close(); + + assertTrue("Environment should be recreated and go into unknown state", + unknownLatch.await(WAIT_STATE_CHANGE_TIMEOUT, TimeUnit.SECONDS)); + + assertEquals("Node made master an unexpected number of times", 1, masterStateChangeCount.get()); + assertEquals("Node made unknown an unexpected number of times", 1, unknownStateChangeCount.get()); + } + + public void testEnvironmentFacadeDetectsRemovalOfRemoteNode() throws Exception + { + final CountDownLatch nodeRemovedLatch = new CountDownLatch(1); + final CountDownLatch nodeAddedLatch = new CountDownLatch(1); + final AtomicReference<ReplicationNode> addedNodeRef = new AtomicReference<ReplicationNode>(); + final AtomicReference<ReplicationNode> removedNodeRef = new AtomicReference<ReplicationNode>(); + ReplicationGroupListener listener = new NoopReplicationGroupListener() + { + @Override + public void onReplicationNodeAddedToGroup(ReplicationNode node) + { + if (addedNodeRef.compareAndSet(null, node)) + { + nodeAddedLatch.countDown(); + } + } + + @Override + public void onReplicationNodeRemovedFromGroup(ReplicationNode node) + { + removedNodeRef.set(node); + nodeRemovedLatch.countDown(); + } + }; + + TestStateChangeListener stateChangeListener = new TestStateChangeListener(State.MASTER); + final ReplicatedEnvironmentFacade masterEnvironment = addNode(State.MASTER, stateChangeListener, listener); + assertTrue("Master was not started", stateChangeListener.awaitForStateChange(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + + masterEnvironment.setDesignatedPrimary(true); + + int replica1Port = getNextAvailable(TEST_NODE_PORT + 1); + String node1NodeHostPort = "localhost:" + replica1Port; + + String replicaName = TEST_NODE_NAME + "_1"; + addReplica(replicaName, node1NodeHostPort); + + assertTrue("Node should be added", nodeAddedLatch.await(WAIT_STATE_CHANGE_TIMEOUT, TimeUnit.SECONDS)); + + ReplicationNode node = addedNodeRef.get(); + assertEquals("Unexpected node name", replicaName, node.getName()); + + // Need to poll to await the remote node updating itself + long timeout = System.currentTimeMillis() + 5000; + while(!State.REPLICA.name().equals(node.getAttribute(ReplicationNode.ROLE)) && System.currentTimeMillis() < timeout) + { + Thread.sleep(200); + } + assertEquals("Unexpected node role", State.REPLICA.name(), node.getAttribute(ReplicationNode.ROLE)); + + // removing remote node + node.setDesiredState(node.getActualState(), org.apache.qpid.server.model.State.DELETED); + + assertTrue("Node deleting is undetected by the environment facade", nodeRemovedLatch.await(WAIT_STATE_CHANGE_TIMEOUT, TimeUnit.SECONDS)); + assertEquals("Unexpected node is deleted", node, removedNodeRef.get()); + + //TODO: need a way to shut down the remote environment when the corresponding remote node is deleted. + // It is unclear whether it is possible + } + + public void testCloseStateTransitions() throws Exception + { + ReplicatedEnvironmentFacade replicatedEnvironmentFacade = createMaster(); + + assertEquals("Unexpected state " + replicatedEnvironmentFacade.getFacadeState(), ReplicatedEnvironmentFacade.State.OPEN, replicatedEnvironmentFacade.getFacadeState()); + replicatedEnvironmentFacade.close(); + assertEquals("Unexpected state " + replicatedEnvironmentFacade.getFacadeState(), ReplicatedEnvironmentFacade.State.CLOSED, replicatedEnvironmentFacade.getFacadeState()); + } + + private ReplicatedEnvironmentFacade createMaster() throws Exception + { + TestStateChangeListener stateChangeListener = new TestStateChangeListener(State.MASTER); + ReplicatedEnvironmentFacade env = addNode(State.MASTER, stateChangeListener, new NoopReplicationGroupListener()); + assertTrue("Environment was not created", stateChangeListener.awaitForStateChange(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + return env; + } + + private ReplicatedEnvironmentFacade addReplica(String nodeName, String nodeHostPort) throws Exception + { + return addReplica(nodeName, nodeHostPort, new NoopReplicationGroupListener()); + } + + private ReplicatedEnvironmentFacade addReplica(String nodeName, String nodeHostPort, ReplicationGroupListener replicationGroupListener) + throws Exception + { + TestStateChangeListener testStateChangeListener = new TestStateChangeListener(State.REPLICA); + ReplicatedEnvironmentFacade replicaEnvironmentFacade = addNode(nodeName, nodeHostPort, TEST_DESIGNATED_PRIMARY, State.REPLICA, testStateChangeListener, replicationGroupListener); + assertTrue("Replica " + nodeName + " was not started", testStateChangeListener.awaitForStateChange(LISTENER_TIMEOUT, TimeUnit.SECONDS)); + return replicaEnvironmentFacade; + } + + private ReplicatedEnvironmentFacade addNode(String nodeName, String nodeHostPort, boolean designatedPrimary, + State desiredState, StateChangeListener stateChangeListener, ReplicationGroupListener replicationGroupListener) + { + LocalReplicationNode node = createReplicationNodeMock(nodeName, nodeHostPort, designatedPrimary); + ReplicatedEnvironmentFacade ref = new ReplicatedEnvironmentFacade(node, _remoteReplicationNodeFactory); + ref.setReplicationGroupListener(replicationGroupListener); + ref.setStateChangeListener(stateChangeListener); + _nodes.put(nodeName, ref); + return ref; + } + + private ReplicatedEnvironmentFacade addNode(State desiredState, StateChangeListener stateChangeListener, ReplicationGroupListener groupChangeListener) + { + return addNode(TEST_NODE_NAME, TEST_NODE_HOST_PORT, TEST_DESIGNATED_PRIMARY, desiredState, stateChangeListener, groupChangeListener); + } + + private DatabaseConfig createDatabase(ReplicatedEnvironmentFacade environmentFacade, String databaseName) throws AMQStoreException + { + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + environmentFacade.openDatabases(dbConfig, databaseName); + return dbConfig; + } + + private LocalReplicationNode createReplicationNodeMock(String nodeName, String nodeHostPort, boolean designatedPrimary) + { + LocalReplicationNode node = mock(LocalReplicationNode.class); + when(node.getAttribute(NAME)).thenReturn(nodeName); + when(node.getName()).thenReturn(nodeName); + when(node.getAttribute(HOST_PORT)).thenReturn(nodeHostPort); + when(node.getAttribute(DESIGNATED_PRIMARY)).thenReturn(designatedPrimary); + when(node.getAttribute(QUORUM_OVERRIDE)).thenReturn(TEST_ELECTABLE_GROUP_OVERRIDE); + when(node.getAttribute(PRIORITY)).thenReturn(TEST_PRIORITY); + when(node.getAttribute(GROUP_NAME)).thenReturn(TEST_GROUP_NAME); + when(node.getAttribute(HELPER_HOST_PORT)).thenReturn(TEST_NODE_HELPER_HOST_PORT); + when(node.getAttribute(DURABILITY)).thenReturn(TEST_DURABILITY); + when(node.getAttribute(COALESCING_SYNC)).thenReturn(TEST_COALESCING_SYNC); + + // TODO REF contract with LRN is too complicated. + when(node.getActualAttribute(HOST_PORT)).thenReturn(nodeHostPort); + when(node.getActualAttribute(DESIGNATED_PRIMARY)).thenReturn(designatedPrimary); + when(node.getActualAttribute(QUORUM_OVERRIDE)).thenReturn(TEST_ELECTABLE_GROUP_OVERRIDE); + when(node.getActualAttribute(PRIORITY)).thenReturn(TEST_PRIORITY); + when(node.getActualAttribute(GROUP_NAME)).thenReturn(TEST_GROUP_NAME); + when(node.getActualAttribute(HELPER_HOST_PORT)).thenReturn(TEST_NODE_HELPER_HOST_PORT); + when(node.getActualAttribute(DURABILITY)).thenReturn(TEST_DURABILITY); + when(node.getActualAttribute(COALESCING_SYNC)).thenReturn(TEST_COALESCING_SYNC); + + Map<String, String> repConfig = new HashMap<String, String>(); + repConfig.put(ReplicationConfig.REPLICA_ACK_TIMEOUT, "2 s"); + repConfig.put(ReplicationConfig.INSUFFICIENT_REPLICAS_TIMEOUT, "2 s"); + when(node.getActualAttribute(REPLICATION_PARAMETERS)).thenReturn(repConfig); + + when(node.getAttribute(STORE_PATH)).thenReturn(new File(_storePath, nodeName).getAbsolutePath()); + return node; + } +} diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/TestStateChangeListener.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/TestStateChangeListener.java new file mode 100644 index 0000000000..1e244e1f89 --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/TestStateChangeListener.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb.replication; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.sleepycat.je.rep.StateChangeEvent; +import com.sleepycat.je.rep.StateChangeListener; +import com.sleepycat.je.rep.ReplicatedEnvironment.State; + +class TestStateChangeListener implements StateChangeListener +{ + private final State _expectedState; + private final CountDownLatch _latch; + + public TestStateChangeListener(State expectedState) + { + _expectedState = expectedState; + _latch = new CountDownLatch(1); + } + + @Override + public void stateChange(StateChangeEvent stateChangeEvent) throws RuntimeException + { + if (stateChangeEvent.getState() == _expectedState) + { + _latch.countDown(); + } + } + + public boolean awaitForStateChange(long timeout, TimeUnit timeUnit) throws InterruptedException + { + return _latch.await(timeout, timeUnit); + } +}
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplateTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplateTest.java index 7ec442b73d..e3b940e6a0 100644 --- a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplateTest.java +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplateTest.java @@ -26,6 +26,7 @@ import static org.mockito.Matchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; + import junit.framework.TestCase; import com.sleepycat.je.Database; diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6Test.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6Test.java index 44f0861275..701fd94115 100644 --- a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6Test.java +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6Test.java @@ -47,7 +47,6 @@ import org.apache.qpid.server.model.Exchange; import org.apache.qpid.server.model.LifetimePolicy; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.UUIDGenerator; -import org.apache.qpid.server.queue.AMQQueueFactory; import org.apache.qpid.server.queue.QueueArgumentsConverter; import org.apache.qpid.server.store.berkeleydb.entry.Xid; import org.apache.qpid.server.store.berkeleydb.tuple.XidBinding; diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgraderFailOnNewerVersionTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgraderFailOnNewerVersionTest.java index 75717120b3..55538c4881 100644 --- a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgraderFailOnNewerVersionTest.java +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgraderFailOnNewerVersionTest.java @@ -20,22 +20,15 @@ */ package org.apache.qpid.server.store.berkeleydb.upgrade; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.store.berkeleydb.BDBMessageStore; + import com.sleepycat.bind.tuple.IntegerBinding; -import com.sleepycat.bind.tuple.LongBinding; import com.sleepycat.je.Cursor; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseConfig; import com.sleepycat.je.DatabaseEntry; -import com.sleepycat.je.Environment; -import com.sleepycat.je.EnvironmentConfig; import com.sleepycat.je.OperationStatus; -import com.sleepycat.je.Transaction; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import org.apache.qpid.AMQStoreException; -import org.apache.qpid.server.store.berkeleydb.AbstractBDBMessageStore; -import org.apache.qpid.server.store.berkeleydb.tuple.ContentBinding; public class UpgraderFailOnNewerVersionTest extends AbstractUpgradeTestCase { @@ -102,7 +95,7 @@ public class UpgraderFailOnNewerVersionTest extends AbstractUpgradeTestCase catch(AMQStoreException ex) { assertEquals("Incorrect exception thrown", "Database version 999 is higher than the most recent known version: " - + AbstractBDBMessageStore.VERSION, ex.getMessage()); + + BDBMessageStore.VERSION, ex.getMessage()); } } diff --git a/qpid/java/bdbstore/systests/pom.xml b/qpid/java/bdbstore/systests/pom.xml index 01b87448e2..c25470bf6d 100644 --- a/qpid/java/bdbstore/systests/pom.xml +++ b/qpid/java/bdbstore/systests/pom.xml @@ -45,6 +45,13 @@ </dependency> <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <version>${mockito-version}</version> + <scope>compile</scope> + </dependency> + + <dependency> <groupId>org.apache.qpid</groupId> <artifactId>qpid-systests</artifactId> <version>${project.version}</version> diff --git a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java index cecd39381e..bd57edfaa8 100644 --- a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java +++ b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java @@ -20,11 +20,15 @@ */ package org.apache.qpid.server.store.berkeleydb; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import java.io.File; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import java.util.UUID; + import org.apache.qpid.AMQStoreException; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.framing.BasicContentHeaderProperties; @@ -41,12 +45,14 @@ import org.apache.qpid.server.message.MessageReference; import org.apache.qpid.server.message.ServerMessage; import org.apache.qpid.server.model.UUIDGenerator; import org.apache.qpid.server.protocol.v0_8.MessageMetaDataType_0_8; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler; import org.apache.qpid.server.store.MessageStoreTest; import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.store.StorableMessageMetaData; import org.apache.qpid.server.store.StoredMessage; import org.apache.qpid.server.store.Transaction; import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler.StoredMessageRecoveryHandler; import org.apache.qpid.transport.DeliveryProperties; import org.apache.qpid.transport.Header; import org.apache.qpid.transport.MessageAcceptMode; @@ -73,7 +79,7 @@ public class BDBMessageStoreTest extends MessageStoreTest { MessageStore store = getVirtualHost().getMessageStore(); - AbstractBDBMessageStore bdbStore = assertBDBStore(store); + BDBMessageStore bdbStore = assertBDBStore(store); // Create content ByteBuffers. // Split the content into 2 chunks for the 0-8 message, as per broker behaviour. @@ -126,7 +132,7 @@ public class BDBMessageStoreTest extends MessageStoreTest /* * reload the store only (read-only) */ - AbstractBDBMessageStore readOnlyStore = reloadStore(bdbStore); + BDBMessageStore readOnlyStore = reloadStore(bdbStore); /* * Read back and validate the 0-8 message metadata and content @@ -225,14 +231,17 @@ public class BDBMessageStoreTest extends MessageStoreTest * Use this method instead of reloading the virtual host like other tests in order * to avoid the recovery handler deleting the message for not being on a queue. */ - private AbstractBDBMessageStore reloadStore(AbstractBDBMessageStore messageStore) throws Exception + private BDBMessageStore reloadStore(BDBMessageStore messageStore) throws Exception { messageStore.close(); - AbstractBDBMessageStore newStore = new BDBMessageStore(); - newStore.configure(getVirtualHostModel(),true); + BDBMessageStore newStore = new BDBMessageStore(); + + MessageStoreRecoveryHandler recoveryHandler = mock(MessageStoreRecoveryHandler.class); + when(recoveryHandler.begin()).thenReturn(mock(StoredMessageRecoveryHandler.class)); + newStore.configureMessageStore(getVirtualHostModel(), recoveryHandler, null); - newStore.startWithNoRecover(); + newStore.activate(); return newStore; } @@ -287,7 +296,7 @@ public class BDBMessageStoreTest extends MessageStoreTest public void testGetContentWithOffset() throws Exception { MessageStore store = getVirtualHost().getMessageStore(); - AbstractBDBMessageStore bdbStore = assertBDBStore(store); + BDBMessageStore bdbStore = assertBDBStore(store); StoredMessage<MessageMetaData> storedMessage_0_8 = createAndStoreSingleChunkMessage_0_8(store); long messageid_0_8 = storedMessage_0_8.getMessageNumber(); @@ -347,7 +356,7 @@ public class BDBMessageStoreTest extends MessageStoreTest public void testMessageCreationAndRemoval() throws Exception { MessageStore store = getVirtualHost().getMessageStore(); - AbstractBDBMessageStore bdbStore = assertBDBStore(store); + BDBMessageStore bdbStore = assertBDBStore(store); StoredMessage<MessageMetaData> storedMessage_0_8 = createAndStoreSingleChunkMessage_0_8(store); long messageid_0_8 = storedMessage_0_8.getMessageNumber(); @@ -372,15 +381,15 @@ public class BDBMessageStoreTest extends MessageStoreTest assertEquals("Retrieved content when none was expected", 0, bdbStore.getContent(messageid_0_8, 0, dst)); } - private AbstractBDBMessageStore assertBDBStore(MessageStore store) + private BDBMessageStore assertBDBStore(MessageStore store) { assertEquals("Test requires an instance of BDBMessageStore to proceed", BDBMessageStore.class, store.getClass()); - return (AbstractBDBMessageStore) store; + return (BDBMessageStore) store; } - private StoredMessage<MessageMetaData> createAndStoreSingleChunkMessage_0_8(MessageStore store) + private StoredMessage<MessageMetaData> createAndStoreSingleChunkMessage_0_8(MessageStore store) throws AMQStoreException { ByteBuffer chunk1 = ByteBuffer.wrap(CONTENT_BYTES); @@ -410,7 +419,7 @@ public class BDBMessageStoreTest extends MessageStoreTest { MessageStore log = getVirtualHost().getMessageStore(); - AbstractBDBMessageStore bdbStore = assertBDBStore(log); + BDBMessageStore bdbStore = assertBDBStore(log); final UUID mockQueueId = UUIDGenerator.generateRandomUUID(); TransactionLogResource mockQueue = new TransactionLogResource() @@ -460,7 +469,7 @@ public class BDBMessageStoreTest extends MessageStoreTest { MessageStore log = getVirtualHost().getMessageStore(); - AbstractBDBMessageStore bdbStore = assertBDBStore(log); + BDBMessageStore bdbStore = assertBDBStore(log); final UUID mockQueueId = UUIDGenerator.generateRandomUUID(); TransactionLogResource mockQueue = new TransactionLogResource() @@ -506,7 +515,7 @@ public class BDBMessageStoreTest extends MessageStoreTest public void testOnDelete() throws Exception { MessageStore log = getVirtualHost().getMessageStore(); - AbstractBDBMessageStore bdbStore = assertBDBStore(log); + BDBMessageStore bdbStore = assertBDBStore(log); String storeLocation = bdbStore.getStoreLocation(); File location = new File(storeLocation); @@ -529,7 +538,7 @@ public class BDBMessageStoreTest extends MessageStoreTest { MessageStore log = getVirtualHost().getMessageStore(); - AbstractBDBMessageStore bdbStore = assertBDBStore(log); + BDBMessageStore bdbStore = assertBDBStore(log); final UUID mockQueueId = UUIDGenerator.generateRandomUUID(); TransactionLogResource mockQueue = new TransactionLogResource() diff --git a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterBlackboxTest.java b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterBlackboxTest.java index 0464269efc..84b8de7be9 100644 --- a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterBlackboxTest.java +++ b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterBlackboxTest.java @@ -20,6 +20,10 @@ package org.apache.qpid.server.store.berkeleydb; import java.io.File; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -30,6 +34,7 @@ import org.apache.log4j.Logger; import org.apache.qpid.client.AMQConnection; import org.apache.qpid.jms.ConnectionListener; import org.apache.qpid.jms.ConnectionURL; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.test.utils.QpidBrokerTestCase; import org.apache.qpid.test.utils.TestUtils; @@ -61,7 +66,7 @@ public class HAClusterBlackboxTest extends QpidBrokerTestCase setSystemProperty("java.util.logging.config.file", "etc" + File.separator + "log.properties"); - _clusterCreator.configureClusterNodes(); + _clusterCreator.configureClusterNodes(null); _brokerFailoverUrl = _clusterCreator.getConnectionUrlForAllClusterNodes(); @@ -115,6 +120,126 @@ public class HAClusterBlackboxTest extends QpidBrokerTestCase connection.createSession(false, Session.AUTO_ACKNOWLEDGE); } + public void testTransferMasterFromLocalNode() throws Exception + { + final Connection connection = getConnection(_brokerFailoverUrl); + + ((AMQConnection)connection).setConnectionListener(_failoverAwaitingListener); + + final int activeBrokerPort = _clusterCreator.getBrokerPortNumberFromConnection(connection); + LOGGER.info("Active connection port " + activeBrokerPort); + + final int inactiveBrokerPort = _clusterCreator.getPortNumberOfAnInactiveBroker(connection); + LOGGER.info("Update role attribute on inactive broker on port " + inactiveBrokerPort); + + Map<String, Object> attributes = _clusterCreator.getReplicationNodeAttributes(inactiveBrokerPort); + assertEquals("Inactive broker has unexpeced role", "REPLICA", attributes.get(ReplicationNode.ROLE)); + _clusterCreator.setReplicationNodeAttributes(inactiveBrokerPort, Collections.<String, Object>singletonMap(ReplicationNode.ROLE, "MASTER")); + + _failoverAwaitingListener.assertFailoverOccurs(20000); + LOGGER.info("Listener has finished"); + + attributes = _clusterCreator.getReplicationNodeAttributes(inactiveBrokerPort); + assertEquals("Inactive broker has unexpeced role", "MASTER", attributes.get(ReplicationNode.ROLE)); + + assertProducingConsuming(connection); + } + + public void testTransferMasterFromRemoteNode() throws Exception + { + final Connection connection = getConnection(_brokerFailoverUrl); + + ((AMQConnection)connection).setConnectionListener(_failoverAwaitingListener); + + final int activeBrokerPort = _clusterCreator.getBrokerPortNumberFromConnection(connection); + LOGGER.info("Active connection port " + activeBrokerPort); + + final int inactiveBrokerPort = _clusterCreator.getPortNumberOfAnInactiveBroker(connection); + LOGGER.info("Update role attribute on inactive broker on port " + inactiveBrokerPort); + + String nodeName = _clusterCreator.getNodeNameForBrokerPort(inactiveBrokerPort); + _clusterCreator.awaitNodeToAttainRole(activeBrokerPort, nodeName, "REPLICA"); + Map<String, Object> attributes = _clusterCreator.getReplicationNodeAttributes(activeBrokerPort, nodeName); + assertEquals("Inactive broker has unexpeced role", "REPLICA", attributes.get(ReplicationNode.ROLE)); + + _clusterCreator.setReplicationNodeAttributes(activeBrokerPort, nodeName, Collections.<String, Object>singletonMap(ReplicationNode.ROLE, "MASTER")); + + _failoverAwaitingListener.assertFailoverOccurs(20000); + LOGGER.info("Listener has finished"); + + attributes = _clusterCreator.getReplicationNodeAttributes(inactiveBrokerPort); + assertEquals("Inactive broker has unexpeced role", "MASTER", attributes.get(ReplicationNode.ROLE)); + + assertProducingConsuming(connection); + } + public void testQuorumOverride() throws Exception + { + final Connection connection = getConnection(_brokerFailoverUrl); + + ((AMQConnection)connection).setConnectionListener(_failoverAwaitingListener); + + Set<Integer> ports = _clusterCreator.getBrokerPortNumbersForNodes(); + Iterator<Integer> iterator = ports.iterator(); + Integer quorumOverridePort = iterator.next(); + iterator.remove(); + + for (Integer p : ports) + { + _clusterCreator.stopNode(p); + } + + Map<String, Object> attributes = _clusterCreator.getReplicationNodeAttributes(quorumOverridePort); + assertEquals("Broker has unexpeced quorum override", new Integer(0), attributes.get(ReplicationNode.QUORUM_OVERRIDE)); + _clusterCreator.setReplicationNodeAttributes(quorumOverridePort, Collections.<String, Object>singletonMap(ReplicationNode.QUORUM_OVERRIDE, 1)); + + _failoverAwaitingListener.assertFailoverOccurs(20000); + LOGGER.info("Listener has finished"); + + attributes = _clusterCreator.getReplicationNodeAttributes(quorumOverridePort); + assertEquals("Broker has unexpeced quorum override", new Integer(1), attributes.get(ReplicationNode.QUORUM_OVERRIDE)); + + assertProducingConsuming(connection); + } + + public void testPriority() throws Exception + { + final Connection connection = getConnection(_brokerFailoverUrl); + + ((AMQConnection)connection).setConnectionListener(_failoverAwaitingListener); + + final int activeBrokerPort = _clusterCreator.getBrokerPortNumberFromConnection(connection); + LOGGER.info("Active connection port " + activeBrokerPort); + + int priority = 1; + Integer highestPriorityBrokerPort = null; + Set<Integer> ports = _clusterCreator.getBrokerPortNumbersForNodes(); + for (Integer port : ports) + { + if (activeBrokerPort != port.intValue()) + { + priority = priority + 1; + highestPriorityBrokerPort = port; + _clusterCreator.setReplicationNodeAttributes(port, Collections.<String, Object>singletonMap(ReplicationNode.PRIORITY, priority)); + } + } + + LOGGER.info("Broker on port " + highestPriorityBrokerPort + " has the highest priority of " + priority); + + Map<String, Object> attributes = _clusterCreator.getReplicationNodeAttributes(highestPriorityBrokerPort); + assertEquals("Broker has unexpeced priority", priority, attributes.get(ReplicationNode.PRIORITY)); + + LOGGER.info("Shutting down the MASTER"); + _clusterCreator.stopNode(activeBrokerPort); + + _failoverAwaitingListener.assertFailoverOccurs(20000); + LOGGER.info("Listener has finished"); + + attributes = _clusterCreator.getReplicationNodeAttributes(highestPriorityBrokerPort); + assertEquals("Inactive broker has unexpeced role", "MASTER", attributes.get(ReplicationNode.ROLE)); + + assertProducingConsuming(connection); + } + private final class FailoverAwaitingListener implements ConnectionListener { private final CountDownLatch _failoverLatch = new CountDownLatch(1); diff --git a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterManagementTest.java b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterManagementTest.java index 4b50121a7a..9adc834f95 100644 --- a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterManagementTest.java +++ b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterManagementTest.java @@ -41,8 +41,6 @@ import org.apache.qpid.server.store.berkeleydb.jmx.ManagedBDBHAMessageStore; import org.apache.qpid.test.utils.JMXTestUtils; import org.apache.qpid.test.utils.QpidBrokerTestCase; -import com.sleepycat.je.EnvironmentFailureException; - /** * System test verifying the ability to control a cluster via the Management API. * @@ -68,7 +66,7 @@ public class HAClusterManagementTest extends QpidBrokerTestCase { _brokerType = BrokerType.SPAWNED; - _clusterCreator.configureClusterNodes(); + _clusterCreator.configureClusterNodes(null); _brokerFailoverUrl = _clusterCreator.getConnectionUrlForAllClusterNodes(); _clusterCreator.startCluster(); @@ -143,11 +141,11 @@ public class HAClusterManagementTest extends QpidBrokerTestCase CompositeData row = groupMembers.get(new Object[] {nodeName}); assertNotNull("Table does not contain row for node name " + nodeName, row); - assertEquals(nodeHostPort, row.get(BDBHAMessageStore.GRP_MEM_COL_NODE_HOST_PORT)); + assertEquals(nodeHostPort, row.get(ManagedBDBHAMessageStore.GRP_MEM_COL_NODE_HOST_PORT)); } } - public void testRemoveNodeFromGroup() throws Exception + public void testRemoveRemoteNodeFromGroup() throws Exception { final Iterator<Integer> brokerPortNumberIterator = getBrokerPortNumbers().iterator(); final int brokerPortNumberToMakeObservation = brokerPortNumberIterator.next(); @@ -155,72 +153,19 @@ public class HAClusterManagementTest extends QpidBrokerTestCase final ManagedBDBHAMessageStore storeBean = getStoreBeanForNodeAtBrokerPort(brokerPortNumberToMakeObservation); awaitAllNodesJoiningGroup(storeBean, NUMBER_OF_NODES); - final String removedNodeName = _clusterCreator.getNodeNameForNodeAt(_clusterCreator.getBdbPortForBrokerPort(brokerPortNumberToBeRemoved)); + final String removedNodeName = _clusterCreator.getNodeNameForBrokerPort(brokerPortNumberToBeRemoved); _clusterCreator.stopNode(brokerPortNumberToBeRemoved); - storeBean.removeNodeFromGroup(removedNodeName); - - final int numberOfDataRowsAfterRemoval = storeBean.getAllNodesInGroup().size(); - assertEquals("Unexpected number of data rows before test", NUMBER_OF_NODES - 1,numberOfDataRowsAfterRemoval); - } - - /** - * Updates the address of a node. - * - * If the broker (node) can subsequently start without error then the update was a success, hence no need for an explicit - * assert. - * - * @see #testRestartNodeWithNewPortNumberWithoutFirstCallingUpdateAddressThrowsAnException() for converse case - */ - public void testUpdateAddress() throws Exception - { - final Iterator<Integer> brokerPortNumberIterator = getBrokerPortNumbers().iterator(); - final int brokerPortNumberToPerformUpdate = brokerPortNumberIterator.next(); - final int brokerPortNumberToBeMoved = brokerPortNumberIterator.next(); - final ManagedBDBHAMessageStore storeBean = getStoreBeanForNodeAtBrokerPort(brokerPortNumberToPerformUpdate); - - _clusterCreator.stopNode(brokerPortNumberToBeMoved); - - final int oldBdbPort = _clusterCreator.getBdbPortForBrokerPort(brokerPortNumberToBeMoved); - final int newBdbPort = getNextAvailable(oldBdbPort + 1); - - storeBean.updateAddress(_clusterCreator.getNodeNameForNodeAt(oldBdbPort), _clusterCreator.getIpAddressOfBrokerHost(), newBdbPort); - - _clusterCreator.modifyClusterNodeBdbAddress(brokerPortNumberToBeMoved, newBdbPort); - - _clusterCreator.startNode(brokerPortNumberToBeMoved); - } - - /** - * @see #testUpdateAddress() - */ - public void testRestartNodeWithNewPortNumberWithoutFirstCallingUpdateAddressThrowsAnException() throws Exception - { - final Iterator<Integer> brokerPortNumberIterator = getBrokerPortNumbers().iterator(); - final int brokerPortNumberToBeMoved = brokerPortNumberIterator.next(); - - _clusterCreator.stopNode(brokerPortNumberToBeMoved); - - final int oldBdbPort = _clusterCreator.getBdbPortForBrokerPort(brokerPortNumberToBeMoved); - final int newBdbPort = getNextAvailable(oldBdbPort + 1); - - // now deliberately don't call updateAddress - _clusterCreator.modifyClusterNodeBdbAddress(brokerPortNumberToBeMoved, newBdbPort); + storeBean.removeNodeFromGroup(removedNodeName); - try + long limitTime = System.currentTimeMillis() + 5000; + while((NUMBER_OF_NODES == storeBean.getAllNodesInGroup().size()) && System.currentTimeMillis() < limitTime) { - _clusterCreator.startNode(brokerPortNumberToBeMoved); - fail("Exception not thrown"); - } - catch(RuntimeException rte) - { - //check cause was BDBs EnvironmentFailureException - assertTrue("Message '"+rte.getMessage()+"' does not contain '" - + EnvironmentFailureException.class.getName() - + "'.", - rte.getMessage().contains(EnvironmentFailureException.class.getName())); - // PASS + Thread.sleep(100l); } + + int numberOfDataRowsAfterRemoval = storeBean.getAllNodesInGroup().size(); + assertEquals("Unexpected number of data rows after test", NUMBER_OF_NODES - 1, numberOfDataRowsAfterRemoval); } public void testVirtualHostOperationsDeniedForNonMasterNode() throws Exception @@ -253,6 +198,20 @@ public class HAClusterManagementTest extends QpidBrokerTestCase } } + public void testSetDesignatedPrimary() throws Exception + { + int brokerPort = _clusterCreator.getBrokerPortNumbersForNodes().iterator().next(); + final ManagedBDBHAMessageStore storeBean = getStoreBeanForNodeAtBrokerPort(brokerPort); + assertFalse("Unexpected designated primary before change", storeBean.getDesignatedPrimary()); + storeBean.setDesignatedPrimary(true); + long limit = System.currentTimeMillis() + 5000; + while(!storeBean.getDesignatedPrimary() && System.currentTimeMillis() < limit) + { + Thread.sleep(100l); + } + assertTrue("Unexpected designated primary after change", storeBean.getDesignatedPrimary()); + } + private ManagedBDBHAMessageStore getStoreBeanForNodeAtBrokerPort(final int brokerPortNumber) throws Exception { _jmxUtils.open(brokerPortNumber); diff --git a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterTwoNodeTest.java b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterTwoNodeTest.java index 95626f7fa5..0031b024ba 100644 --- a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterTwoNodeTest.java +++ b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterTwoNodeTest.java @@ -20,33 +20,30 @@ package org.apache.qpid.server.store.berkeleydb; import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import javax.jms.Connection; import javax.jms.Destination; import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageConsumer; import javax.jms.Session; -import javax.management.ObjectName; import org.apache.qpid.jms.ConnectionURL; -import org.apache.qpid.server.store.berkeleydb.jmx.ManagedBDBHAMessageStore; -import org.apache.qpid.test.utils.JMXTestUtils; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.test.utils.QpidBrokerTestCase; import com.sleepycat.je.rep.ReplicationConfig; public class HAClusterTwoNodeTest extends QpidBrokerTestCase { - private static final long RECEIVE_TIMEOUT = 5000l; - private static final String VIRTUAL_HOST = "test"; - private static final String MANAGED_OBJECT_QUERY = "org.apache.qpid:type=BDBHAMessageStore,name=" + ObjectName.quote(VIRTUAL_HOST); + public static final long RECEIVE_TIMEOUT = 5000l; + private static final int NUMBER_OF_NODES = 2; private final HATestClusterCreator _clusterCreator = new HATestClusterCreator(this, VIRTUAL_HOST, NUMBER_OF_NODES); - private final JMXTestUtils _jmxUtils = new JMXTestUtils(this); private ConnectionURL _brokerFailoverUrl; @@ -62,19 +59,6 @@ public class HAClusterTwoNodeTest extends QpidBrokerTestCase } @Override - protected void tearDown() throws Exception - { - try - { - _jmxUtils.close(); - } - finally - { - super.tearDown(); - } - } - - @Override public void startBroker() throws Exception { // Don't start default broker provided by QBTC. @@ -84,16 +68,12 @@ public class HAClusterTwoNodeTest extends QpidBrokerTestCase { setSystemProperty("java.util.logging.config.file", "etc" + File.separator + "log.properties"); - String storeConfigKeyPrefix = _clusterCreator.getStoreConfigKeyPrefix(); - - setVirtualHostConfigurationProperty(storeConfigKeyPrefix + ".repConfig(0).name", ReplicationConfig.INSUFFICIENT_REPLICAS_TIMEOUT); - setVirtualHostConfigurationProperty(storeConfigKeyPrefix + ".repConfig(0).value", "2 s"); - - setVirtualHostConfigurationProperty(storeConfigKeyPrefix + ".repConfig(1).name", ReplicationConfig.ELECTIONS_PRIMARY_RETRIES); - setVirtualHostConfigurationProperty(storeConfigKeyPrefix + ".repConfig(1).value", "0"); + Map<String,String> replicationParameters = new HashMap<String, String>(); + replicationParameters.put(ReplicationConfig.INSUFFICIENT_REPLICAS_TIMEOUT, "2 s"); + replicationParameters.put(ReplicationConfig.ELECTIONS_PRIMARY_RETRIES, "0"); - _clusterCreator.configureClusterNodes(); - _clusterCreator.setDesignatedPrimaryOnFirstBroker(designedPrimary); + _clusterCreator.configureClusterNodes(replicationParameters); + _clusterCreator.configureDesignatedPrimaryOnFirstBroker(designedPrimary); _brokerFailoverUrl = _clusterCreator.getConnectionUrlForAllClusterNodes(); _clusterCreator.startCluster(); } @@ -143,7 +123,7 @@ public class HAClusterTwoNodeTest extends QpidBrokerTestCase try { assertProducingConsuming(connection); - fail("JMS peristent operations succeded on Master 'not designated primary' buy they should fail as replica is not available"); + fail("JMS peristent operations succeded on Master 'not designated primary' but they should fail as replica is not available"); } catch(JMSException e) { @@ -170,48 +150,79 @@ public class HAClusterTwoNodeTest extends QpidBrokerTestCase public void testInitialDesignatedPrimaryStateOfNodes() throws Exception { startCluster(true); - final ManagedBDBHAMessageStore primaryStoreBean = getStoreBeanForNodeAtBrokerPort(_clusterCreator.getBrokerPortNumberOfPrimary()); - assertTrue("Expected primary node to be set as designated primary", primaryStoreBean.getDesignatedPrimary()); + Map<String, Object> primaryAttributes = _clusterCreator.getReplicationNodeAttributes(_clusterCreator.getBrokerPortNumberOfPrimary()); + assertTrue("Expected primary node to be set as designated primary", (Boolean)primaryAttributes.get(ReplicationNode.DESIGNATED_PRIMARY)); - final ManagedBDBHAMessageStore secondaryStoreBean = getStoreBeanForNodeAtBrokerPort(_clusterCreator.getBrokerPortNumberOfSecondaryNode()); - assertFalse("Expected secondary node to NOT be set as designated primary", secondaryStoreBean.getDesignatedPrimary()); + Map<String, Object> secondaryAttributes = _clusterCreator.getReplicationNodeAttributes(_clusterCreator.getBrokerPortNumberOfSecondaryNode()); + assertFalse("Expected secondary node to NOT be set as designated primary", (Boolean)secondaryAttributes.get(ReplicationNode.DESIGNATED_PRIMARY)); } public void testSecondaryDesignatedAsPrimaryAfterOrginalPrimaryStopped() throws Exception { startCluster(true); _clusterCreator.stopNode(_clusterCreator.getBrokerPortNumberOfPrimary()); - final ManagedBDBHAMessageStore storeBean = getStoreBeanForNodeAtBrokerPort(_clusterCreator.getBrokerPortNumberOfSecondaryNode()); - assertFalse("Expected node to NOT be set as designated primary", storeBean.getDesignatedPrimary()); - storeBean.setDesignatedPrimary(true); - assertTrue("Expected node to now be set as designated primary", storeBean.getDesignatedPrimary()); + int brokerPortNumberOfSecondaryNode = _clusterCreator.getBrokerPortNumberOfSecondaryNode(); + + Map<String, Object> secondaryAttributes = _clusterCreator.getReplicationNodeAttributes(brokerPortNumberOfSecondaryNode); + assertFalse("Expected node to NOT be set as designated primary", (Boolean)secondaryAttributes.get(ReplicationNode.DESIGNATED_PRIMARY)); + + setDesignatedPrimary(brokerPortNumberOfSecondaryNode, true); + + secondaryAttributes = _clusterCreator.getReplicationNodeAttributes(brokerPortNumberOfSecondaryNode); + assertTrue("Expected node to now be set as designated primary", (Boolean)secondaryAttributes.get(ReplicationNode.DESIGNATED_PRIMARY)); final Connection connection = getConnection(_brokerFailoverUrl); assertNotNull("Expected to get a valid connection to new primary", connection); assertProducingConsuming(connection); } - private ManagedBDBHAMessageStore getStoreBeanForNodeAtBrokerPort( - final int activeBrokerPortNumber) throws Exception + public void testMasterNotDesignatedPrimaryAssignedDesignatedPrimaryLaterOn() throws Exception { - _jmxUtils.open(activeBrokerPortNumber); + startCluster(false); - ManagedBDBHAMessageStore storeBean = _jmxUtils.getManagedObject(ManagedBDBHAMessageStore.class, MANAGED_OBJECT_QUERY); - return storeBean; - } + // Shutdown replica + _clusterCreator.stopNode(_clusterCreator.getBrokerPortNumberOfSecondaryNode()); - private void assertProducingConsuming(final Connection connection) throws JMSException, Exception - { + // Do transaction + final Connection connection = getConnection(_brokerFailoverUrl); Session session = connection.createSession(true, Session.SESSION_TRANSACTED); Destination destination = session.createQueue(getTestQueueName()); - MessageConsumer consumer = session.createConsumer(destination); - sendMessage(session, destination, 1); - connection.start(); - Message m1 = consumer.receive(RECEIVE_TIMEOUT); - assertNotNull("Message 1 is not received", m1); - assertEquals("Unexpected first message received", 0, m1.getIntProperty(INDEX)); - session.commit(); + try + { + session.createConsumer(destination); + fail("Creation of durable queue should fail as there is no majority"); + } + catch (JMSException je) + { + // pass + je.printStackTrace(); + } + + int brokerPortNumberOfPrimary = _clusterCreator.getBrokerPortNumberOfPrimary(); + + // Check master is in UNKNOWN + awaitNodeToAttainRole(brokerPortNumberOfPrimary, "UNKNOWN"); + + // Designate primary + setDesignatedPrimary(brokerPortNumberOfPrimary, true); + + // Check master is MASTER + awaitNodeToAttainRole(brokerPortNumberOfPrimary, "MASTER"); + + final Connection connection2 = getConnection(_brokerFailoverUrl); + assertProducingConsuming(connection2); + } + + private void setDesignatedPrimary(int brokerPort, boolean designatedPrimary) throws Exception + { + _clusterCreator.setReplicationNodeAttributes(brokerPort, Collections.<String, Object>singletonMap(ReplicationNode.DESIGNATED_PRIMARY, designatedPrimary)); + } + + private void awaitNodeToAttainRole(int brokerPort, String desiredRole) throws Exception + { + String nodeName = _clusterCreator.getNodeNameForBrokerPort(brokerPort); + _clusterCreator.awaitNodeToAttainRole(brokerPort, nodeName, desiredRole); } } diff --git a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterWhiteboxTest.java b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterWhiteboxTest.java index 408643b98a..616294c4cc 100644 --- a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterWhiteboxTest.java +++ b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HAClusterWhiteboxTest.java @@ -23,7 +23,6 @@ import java.io.File; import java.util.Set; import javax.jms.Connection; -import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; @@ -60,7 +59,7 @@ public class HAClusterWhiteboxTest extends QpidBrokerTestCase setSystemProperty("java.util.logging.config.file", "etc" + File.separator + "log.properties"); - _clusterCreator.configureClusterNodes(); + _clusterCreator.configureClusterNodes(null); _clusterCreator.startCluster(); super.setUp(); @@ -235,22 +234,6 @@ public class HAClusterWhiteboxTest extends QpidBrokerTestCase killBroker(initialPortNumber); // kill awaits the death of the child } - private void assertProducingConsuming(final Connection connection) throws JMSException, Exception - { - Session session = connection.createSession(true, Session.SESSION_TRANSACTED); - Destination destination = session.createQueue(getTestQueueName()); - MessageConsumer consumer = session.createConsumer(destination); - sendMessage(session, destination, 2); - connection.start(); - Message m1 = consumer.receive(RECEIVE_TIMEOUT); - assertNotNull("Message 1 is not received", m1); - assertEquals("Unexpected first message received", 0, m1.getIntProperty(INDEX)); - Message m2 = consumer.receive(RECEIVE_TIMEOUT); - assertNotNull("Message 2 is not received", m2); - assertEquals("Unexpected second message received", 1, m2.getIntProperty(INDEX)); - session.commit(); - } - private void closeConnection(final Connection initialConnection) { try diff --git a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HATestClusterCreator.java b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HATestClusterCreator.java index 353c3a0ec5..3674f59bba 100644 --- a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HATestClusterCreator.java +++ b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/HATestClusterCreator.java @@ -19,16 +19,19 @@ */ package org.apache.qpid.server.store.berkeleydb; +import java.io.File; +import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; +import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; @@ -38,14 +41,21 @@ import java.util.concurrent.TimeUnit; import javax.jms.Connection; -import org.apache.commons.configuration.XMLConfiguration; -import org.apache.commons.lang.StringUtils; +import junit.framework.Assert; + import org.apache.log4j.Logger; import org.apache.qpid.client.AMQConnection; import org.apache.qpid.client.AMQConnectionURL; -import org.apache.qpid.test.utils.TestBrokerConfiguration; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.management.plugin.HttpManagement; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.systest.rest.RestTestHelper; import org.apache.qpid.test.utils.QpidBrokerTestCase; +import org.apache.qpid.test.utils.TestBrokerConfiguration; import org.apache.qpid.url.URLSyntaxException; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.map.JsonMappingException; public class HATestClusterCreator { @@ -65,17 +75,14 @@ public class HATestClusterCreator private static final int CONNECTDELAY = 75; private final QpidBrokerTestCase _testcase; - private final Map<Integer, Integer> _brokerPortToBdbPortMap = new HashMap<Integer, Integer>(); - private final Map<Integer, BrokerConfigHolder> _brokerConfigurations = new TreeMap<Integer, BrokerConfigHolder>(); + private final Map<Integer, Integer> _brokerPortToBdbPortMap = new TreeMap<Integer, Integer>(); private final String _virtualHostName; - private final String _vhostStoreConfigKeyPrefix; private final String _ipAddressOfBroker; - private final String _groupName ; + private final String _groupName; private final int _numberOfNodes; private int _bdbHelperPort; private int _primaryBrokerPort; - private String _vhostConfigKeyPrefix; public HATestClusterCreator(QpidBrokerTestCase testcase, String virtualHostName, int numberOfNodes) { @@ -84,12 +91,10 @@ public class HATestClusterCreator _groupName = "group" + _testcase.getName(); _ipAddressOfBroker = getIpAddressOfBrokerHost(); _numberOfNodes = numberOfNodes; - _vhostConfigKeyPrefix = "virtualhosts.virtualhost." + _virtualHostName + "."; - _vhostStoreConfigKeyPrefix = _vhostConfigKeyPrefix + "store."; _bdbHelperPort = 0; } - public void configureClusterNodes() throws Exception + public void configureClusterNodes(Map<String,String> replicationParameters) throws Exception { int brokerPort = _testcase.findFreePort(); @@ -104,50 +109,36 @@ public class HATestClusterCreator _bdbHelperPort = bdbPort; } - configureClusterNode(brokerPort, bdbPort); - TestBrokerConfiguration brokerConfiguration = _testcase.getBrokerConfiguration(brokerPort); - brokerConfiguration.addJmxManagementConfiguration(); - collectConfig(brokerPort, brokerConfiguration, _testcase.getTestVirtualhosts()); + configureClusterNodeInBrokerConfiguration(brokerPort, bdbPort, replicationParameters); brokerPort = _testcase.getNextAvailable(bdbPort + 1); } } - public void setDesignatedPrimaryOnFirstBroker(boolean designatedPrimary) throws Exception + public void configureDesignatedPrimaryOnFirstBroker(boolean designatedPrimary) throws Exception { if (_numberOfNodes != 2) { throw new IllegalArgumentException("Only two nodes groups have the concept of primary"); } - - final Entry<Integer, BrokerConfigHolder> brokerConfigEntry = _brokerConfigurations.entrySet().iterator().next(); - final String configKey = getConfigKey("highAvailability.designatedPrimary"); - brokerConfigEntry.getValue().getTestVirtualhosts().setProperty(configKey, Boolean.toString(designatedPrimary)); - _primaryBrokerPort = brokerConfigEntry.getKey(); - } - - /** - * @param configKeySuffix "highAvailability.designatedPrimary", for example - * @return "virtualhost.test.store.highAvailability.designatedPrimary", for example - */ - private String getConfigKey(String configKeySuffix) - { - final String configKey = StringUtils.substringAfter(_vhostStoreConfigKeyPrefix + configKeySuffix, "virtualhosts."); - return configKey; + Map.Entry<Integer, Integer> portsEntry = _brokerPortToBdbPortMap.entrySet().iterator().next(); + TestBrokerConfiguration brokerConfiguration = _testcase.getBrokerConfiguration(portsEntry.getKey()); + String nodeName = getNodeNameForNodeAt(portsEntry.getValue()); + brokerConfiguration.setObjectAttribute(nodeName, ReplicationNode.DESIGNATED_PRIMARY, designatedPrimary); + + // store broker configuration on next restart + brokerConfiguration.setSaved(false); + _primaryBrokerPort = portsEntry.getKey(); } public void startNode(final int brokerPortNumber) throws Exception { - final BrokerConfigHolder brokerConfigHolder = _brokerConfigurations.get(brokerPortNumber); - - _testcase.setTestVirtualhosts(brokerConfigHolder.getTestVirtualhosts()); - _testcase.startBroker(brokerPortNumber); } public void startCluster() throws Exception { - for (final Integer brokerPortNumber : _brokerConfigurations.keySet()) + for (final Integer brokerPortNumber : _brokerPortToBdbPortMap.keySet()) { startNode(brokerPortNumber); } @@ -155,21 +146,19 @@ public class HATestClusterCreator public void startClusterParallel() throws Exception { - final ExecutorService executor = Executors.newFixedThreadPool(_brokerConfigurations.size()); + final ExecutorService executor = Executors.newFixedThreadPool(_brokerPortToBdbPortMap.size()); try { List<Future<Object>> brokers = new CopyOnWriteArrayList<Future<Object>>(); - for (final Integer brokerPortNumber : _brokerConfigurations.keySet()) + for (final Integer brokerPortNumber : _brokerPortToBdbPortMap.keySet()) { - final BrokerConfigHolder brokerConfigHolder = _brokerConfigurations.get(brokerPortNumber); Future<Object> future = executor.submit(new Callable<Object>() { public Object call() { try { - _testcase.startBroker(brokerPortNumber, brokerConfigHolder.getTestConfiguration(), - brokerConfigHolder.getTestVirtualhosts()); + _testcase.startBroker(brokerPortNumber); return "OK"; } catch (Exception e) @@ -213,7 +202,7 @@ public class HATestClusterCreator public void stopCluster() throws Exception { - for (final Integer brokerPortNumber : _brokerConfigurations.keySet()) + for (final Integer brokerPortNumber : _brokerPortToBdbPortMap.keySet()) { try { @@ -301,6 +290,11 @@ public class HATestClusterCreator return _groupName; } + public String getNodeNameForBrokerPort(final int brokerPort) + { + return getNodeNameForNodeAt(_brokerPortToBdbPortMap.get(brokerPort)); + } + public String getNodeNameForNodeAt(final int bdbPort) { return "node" + _testcase.getName() + bdbPort; @@ -345,21 +339,40 @@ public class HATestClusterCreator public Set<Integer> getBrokerPortNumbersForNodes() { - return new HashSet<Integer>(_brokerConfigurations.keySet()); + return new HashSet<Integer>(_brokerPortToBdbPortMap.keySet()); } - private void configureClusterNode(final int brokerPort, final int bdbPort) throws Exception + private void configureClusterNodeInBrokerConfiguration(final int brokerPort, final int bdbPort, Map<String,String> replicationParameters) throws Exception { - final String nodeName = getNodeNameForNodeAt(bdbPort); + String nodeName = getNodeNameForNodeAt(bdbPort); + TestBrokerConfiguration config = _testcase.getBrokerConfiguration(brokerPort); + + //remove default non-ha test virtual host + config.removeObjectConfiguration("test"); + + // replication node + Map<String, Object> replicationNodeAttributes = new HashMap<String, Object>(); + replicationNodeAttributes.put(ReplicationNode.NAME, nodeName); + replicationNodeAttributes.put(ReplicationNode.GROUP_NAME, _groupName); + replicationNodeAttributes.put(ReplicationNode.HOST_PORT, getNodeHostPortForNodeAt(bdbPort)); + replicationNodeAttributes.put(ReplicationNode.HELPER_HOST_PORT, getHelperHostPort()); + if (replicationParameters != null) + { + replicationNodeAttributes.put(ReplicationNode.REPLICATION_PARAMETERS, replicationParameters); + } + replicationNodeAttributes.put(ReplicationNode.STORE_PATH, System.getProperty(BrokerProperties.PROPERTY_QPID_WORK) + File.separator + nodeName); + // ha virtual host + Map<String, Object> virtualHostAttributes = new HashMap<String, Object>(); + virtualHostAttributes.put(VirtualHost.NAME, _virtualHostName); + virtualHostAttributes.put(VirtualHost.TYPE, BDBHAVirtualHostFactory.TYPE); - _testcase.setVirtualHostConfigurationProperty(_vhostConfigKeyPrefix + "type", BDBHAVirtualHostFactory.TYPE); - _testcase.setVirtualHostConfigurationProperty(_vhostStoreConfigKeyPrefix + "class", "org.apache.qpid.server.store.berkeleydb.BDBHAMessageStore"); + UUID hostId = config.addVirtualHostConfiguration(virtualHostAttributes); + config.addReplicationNodeConfiguration(hostId, replicationNodeAttributes); - _testcase.setVirtualHostConfigurationProperty(_vhostStoreConfigKeyPrefix + "highAvailability.groupName", _groupName); - _testcase.setVirtualHostConfigurationProperty(_vhostStoreConfigKeyPrefix + "highAvailability.nodeName", nodeName); - _testcase.setVirtualHostConfigurationProperty(_vhostStoreConfigKeyPrefix + "highAvailability.nodeHostPort", getNodeHostPortForNodeAt(bdbPort)); - _testcase.setVirtualHostConfigurationProperty(_vhostStoreConfigKeyPrefix + "highAvailability.helperHostPort", getHelperHostPort()); + config.addJmxManagementConfiguration(); + config.addHttpManagementConfiguration(); + config.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_HTTP_MANAGEMENT, HttpManagement.HTTP_BASIC_AUTHENTICATION_ENABLED, true); } public String getIpAddressOfBrokerHost() @@ -375,55 +388,68 @@ public class HATestClusterCreator } } - private void collectConfig(final int brokerPortNumber, TestBrokerConfiguration testConfiguration, XMLConfiguration testVirtualhosts) + public void setReplicationNodeAttributes(int brokerPort, Map<String, Object> attributeMap) throws Exception { - _brokerConfigurations.put(brokerPortNumber, new BrokerConfigHolder(testConfiguration, - (XMLConfiguration) testVirtualhosts.clone())); + String replicationNodeName = getNodeNameForBrokerPort(brokerPort); + setReplicationNodeAttributes(brokerPort, replicationNodeName, attributeMap); } - public class BrokerConfigHolder + public void setReplicationNodeAttributes(int brokerPort, String replicationNodeName, Map<String, Object> attributeMap) + throws IOException, JsonGenerationException, JsonMappingException, Exception { - private final TestBrokerConfiguration _testConfiguration; - private final XMLConfiguration _testVirtualhosts; - - public BrokerConfigHolder(TestBrokerConfiguration testConfiguration, XMLConfiguration testVirtualhosts) + RestTestHelper restHelper = createRestTestHelper(brokerPort); + int status = restHelper.submitRequest("/rest/replicationnode/" + _virtualHostName + "/" + replicationNodeName , "PUT", attributeMap); + if (status != 200) { - _testConfiguration = testConfiguration; - _testVirtualhosts = testVirtualhosts; + throw new Exception("Unexpected http status when updating " + replicationNodeName + " attribute's : " + status); } + } + + public Map<String, Object> getReplicationNodeAttributes(int brokerPort) throws Exception + { + String replicationNodeName = getNodeNameForBrokerPort(brokerPort); + return getReplicationNodeAttributes(brokerPort, replicationNodeName); + } - public TestBrokerConfiguration getTestConfiguration() + public Map<String, Object> getReplicationNodeAttributes(int brokerPort, String replicationNodeName) throws IOException + { + RestTestHelper restHelper = createRestTestHelper(brokerPort); + List<Map<String, Object>> results= restHelper.getJsonAsList("/rest/replicationnode/" + _virtualHostName + "/" + replicationNodeName ); + int size = results.size(); + if (size == 0) { - return _testConfiguration; + return Collections.emptyMap(); } - - public XMLConfiguration getTestVirtualhosts() + else if (size == 1) { - return _testVirtualhosts; + return results.get(0); + } + else + { + throw new RuntimeException("Unexpected number of nodes " + size); } } - public void modifyClusterNodeBdbAddress(int brokerPortNumberToBeMoved, int newBdbPort) + private RestTestHelper createRestTestHelper(int brokerPort) { - final BrokerConfigHolder brokerConfigHolder = _brokerConfigurations.get(brokerPortNumberToBeMoved); - final XMLConfiguration virtualHostConfig = brokerConfigHolder.getTestVirtualhosts(); - - final String configKey = getConfigKey("highAvailability.nodeHostPort"); - final String oldBdbHostPort = virtualHostConfig.getString(configKey); - - final String[] oldHostAndPort = StringUtils.split(oldBdbHostPort, ":"); - final String oldHost = oldHostAndPort[0]; - - final String newBdbHostPort = oldHost + ":" + newBdbPort; - - virtualHostConfig.setProperty(configKey, newBdbHostPort); - collectConfig(brokerPortNumberToBeMoved, brokerConfigHolder.getTestConfiguration(), virtualHostConfig); + int httpPort = _testcase.getHttpManagementPort(brokerPort); + return RestTestHelper.createRestTestHelperWithDefaultCredentials(httpPort); } - public String getStoreConfigKeyPrefix() + public void awaitNodeToAttainRole(int brokerPort, String nodeName, String desiredRole) throws Exception { - return _vhostStoreConfigKeyPrefix; - } - + final long startTime = System.currentTimeMillis(); + Map<String, Object> data = Collections.emptyMap(); + while(!desiredRole.equals(data.get(ReplicationNode.ROLE)) && (System.currentTimeMillis() - startTime) < 30000) + { + LOGGER.debug("Awaiting node '" + nodeName + "' to transit into " + desiredRole + " role"); + data = getReplicationNodeAttributes(brokerPort, nodeName); + if (!desiredRole.equals(data.get(ReplicationNode.ROLE))) + { + Thread.sleep(1000); + } + } + Assert.assertEquals("Node is in unexpected role", desiredRole, data.get(ReplicationNode.ROLE)); + } } diff --git a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/ReplicationNodeRestTest.java b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/ReplicationNodeRestTest.java new file mode 100644 index 0000000000..631e329160 --- /dev/null +++ b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/ReplicationNodeRestTest.java @@ -0,0 +1,169 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.systest.rest.QpidRestTestCase; +import org.apache.qpid.util.FileUtils; + +public class ReplicationNodeRestTest extends QpidRestTestCase +{ + private static final String NODE_NAME = "node1"; + private static final String GROUP_NAME = "replication-group"; + + private String _hostName; + private File _storeFile; + private int _haPort; + private String _nodeRestUrl; + private String _hostRestUrl; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _hostName = getTestName(); + _nodeRestUrl = "/rest/replicationnode/" + _hostName + "/" + NODE_NAME; + _hostRestUrl = "/rest/virtualhost/" + _hostName; + + _storeFile = new File(TMP_FOLDER, "store-" + _hostName + "-" + System.currentTimeMillis()); + _haPort = findFreePort(); + + Map<String, Object> hostData = new HashMap<String, Object>(); + hostData.put(VirtualHost.NAME, _hostName); + hostData.put(VirtualHost.TYPE, BDBHAVirtualHostFactory.TYPE); + hostData.put(VirtualHost.DESIRED_STATE, State.QUIESCED); + + int responseCode = getRestTestHelper().submitRequest(_hostRestUrl, "PUT", hostData); + assertEquals("Unexpected response code for virtual host creation request", 201, responseCode); + + String hostPort = "localhost:" + _haPort; + Map<String, Object> nodeData = new HashMap<String, Object>(); + nodeData.put(ReplicationNode.NAME, NODE_NAME); + nodeData.put(ReplicationNode.GROUP_NAME, GROUP_NAME); + nodeData.put(ReplicationNode.HOST_PORT, hostPort); + nodeData.put(ReplicationNode.HELPER_HOST_PORT, hostPort); + nodeData.put(ReplicationNode.STORE_PATH, _storeFile.getAbsolutePath()); + + responseCode = getRestTestHelper().submitRequest(_nodeRestUrl, "PUT", nodeData); + assertEquals("Unexpected response code for node creation request", 201, responseCode); + + } + + @Override + public void tearDown() throws Exception + { + try + { + super.tearDown(); + } + finally + { + if (_storeFile != null) + { + FileUtils.delete(_storeFile, true); + } + } + } + + public void testChangePriority() throws Exception + { + assertReplicationNodeSetAttribute(ReplicationNode.PRIORITY, 1, 2, 3); + } + + public void testChangeQuorumOverride() throws Exception + { + assertReplicationNodeSetAttribute(ReplicationNode.QUORUM_OVERRIDE, 0, 1, 2); + } + + public void testChangeDesignatedPrimary() throws Exception + { + assertReplicationNodeSetAttribute(ReplicationNode.DESIGNATED_PRIMARY, false, true, false); + } + + public void testCreationOfSecondLocalReplicationNodeFails() throws Exception + { + String hostPort = "localhost:" + _haPort; + Map<String, Object> nodeData = new HashMap<String, Object>(); + nodeData.put(ReplicationNode.NAME, NODE_NAME + 1); + nodeData.put(ReplicationNode.GROUP_NAME, GROUP_NAME); + nodeData.put(ReplicationNode.HOST_PORT, hostPort); + nodeData.put(ReplicationNode.HELPER_HOST_PORT, hostPort); + nodeData.put(ReplicationNode.STORE_PATH, _storeFile.getAbsolutePath()); + + int responseCode = getRestTestHelper().submitRequest(_nodeRestUrl + 1, "PUT", nodeData); + assertEquals("Adding of a second replication node should fail", 409, responseCode); + } + + public void testUpdateImmutableAttributeWithTheSameValueSucceeds() throws Exception + { + String hostPort = "localhost:" + _haPort; + Map<String, Object> nodeData = new HashMap<String, Object>(); + nodeData.put(ReplicationNode.NAME, NODE_NAME); + nodeData.put(ReplicationNode.GROUP_NAME, GROUP_NAME); + nodeData.put(ReplicationNode.HOST_PORT, hostPort); + nodeData.put(ReplicationNode.HELPER_HOST_PORT, hostPort); + nodeData.put(ReplicationNode.STORE_PATH, _storeFile.getAbsolutePath()); + + int responseCode = getRestTestHelper().submitRequest(_nodeRestUrl, "PUT", nodeData); + assertEquals("Update with unchanged attribute should succeed", 200, responseCode); + } + + private void assertReplicationNodeSetAttribute(String attributeName, Object initialValue, + Object newValueBeforeHostActivation, Object newValueAfterHostActivation) throws Exception + { + Map<String, Object> nodeAttributes = getRestTestHelper().getJsonAsSingletonList(_nodeRestUrl); + assertEquals("Unexpected " + attributeName + " after creation", initialValue, nodeAttributes.get(attributeName)); + + int responseCode = getRestTestHelper().submitRequest(_nodeRestUrl, "PUT", Collections.<String, Object>singletonMap(attributeName, newValueBeforeHostActivation)); + assertEquals("Unexpected response code for node " + attributeName + " update", 200, responseCode); + + waitForAttributeChanged(attributeName, newValueBeforeHostActivation); + + responseCode = getRestTestHelper().submitRequest(_hostRestUrl, "PUT", Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.ACTIVE)); + assertEquals("Unexpected response code for virtual host update status", 200, responseCode); + + waitForAttributeChanged(attributeName, newValueBeforeHostActivation); + + responseCode = getRestTestHelper().submitRequest(_nodeRestUrl, "PUT", Collections.<String, Object>singletonMap(attributeName, newValueAfterHostActivation)); + assertEquals("Unexpected response code for node " + attributeName + " update", 200, responseCode); + + waitForAttributeChanged(attributeName, newValueAfterHostActivation); + } + + private void waitForAttributeChanged(String attributeName, Object newValue) throws Exception + { + Map<String, Object> nodeAttributes = getRestTestHelper().getJsonAsSingletonList(_nodeRestUrl); + long limit = System.currentTimeMillis() + 5000; + while(!newValue.equals(nodeAttributes.get(attributeName)) && System.currentTimeMillis() < limit) + { + Thread.sleep(100l); + nodeAttributes = getRestTestHelper().getJsonAsSingletonList(_nodeRestUrl); + } + assertEquals("Unexpected attribute " + attributeName, newValue, nodeAttributes.get(attributeName)); + } +} diff --git a/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/VirtualHostRestTest.java b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/VirtualHostRestTest.java new file mode 100644 index 0000000000..b4a3661039 --- /dev/null +++ b/qpid/java/bdbstore/systests/src/main/java/org/apache/qpid/server/store/berkeleydb/VirtualHostRestTest.java @@ -0,0 +1,150 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store.berkeleydb; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.systest.rest.Asserts; +import org.apache.qpid.systest.rest.QpidRestTestCase; +import org.apache.qpid.util.FileUtils; + +public class VirtualHostRestTest extends QpidRestTestCase +{ + + private static final String VIRTUALHOST_NODES_ATTRIBUTE = "replicationnodes"; + private File _storeFile; + private String _hostName; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _hostName = getTestName(); + + _storeFile = new File(TMP_FOLDER, "store-" + _hostName + "-" + System.currentTimeMillis()); + } + + @Override + public void tearDown() throws Exception + { + try + { + super.tearDown(); + } + finally + { + if (_storeFile != null) + { + FileUtils.delete(_storeFile, true); + } + } + } + + public void testPutCreateHAVirtualHost() throws Exception + { + Map<String, Object> hostData = new HashMap<String, Object>(); + hostData.put(VirtualHost.NAME, _hostName); + hostData.put(VirtualHost.TYPE, BDBHAVirtualHostFactory.TYPE); + hostData.put(VirtualHost.DESIRED_STATE, State.QUIESCED); + + int responseCode = getRestTestHelper().submitRequest("/rest/virtualhost/" + _hostName, "PUT", hostData); + assertEquals("Unexpected response code for virtual host creation request", 201, responseCode); + + Map<String, Object> hostDetails = getRestTestHelper().getJsonAsSingletonList("/rest/virtualhost/" + _hostName); + assertEquals("Virtual host in unexpected desired state ", State.QUIESCED.name(), hostDetails.get(VirtualHost.DESIRED_STATE)); + assertEquals("Virtual host in unexpected actual state ", State.QUIESCED.name(), hostDetails.get(VirtualHost.STATE)); + + String storeLocation = _storeFile.getAbsolutePath(); + String nodeName = "node1"; + String groupName = "replication-group"; + int port = findFreePort(); + String hostPort = "localhost:" + port; + + Map<String, Object> nodeData = new HashMap<String, Object>(); + nodeData.put(ReplicationNode.NAME, nodeName); + nodeData.put(ReplicationNode.GROUP_NAME, groupName); + nodeData.put(ReplicationNode.HOST_PORT, hostPort); + nodeData.put(ReplicationNode.HELPER_HOST_PORT, hostPort); + nodeData.put(ReplicationNode.STORE_PATH, storeLocation); + + String createNodeUrl = "/rest/replicationnode/" + _hostName + "/" + nodeName; + responseCode = getRestTestHelper().submitRequest(createNodeUrl, "PUT", nodeData); + assertEquals("Unexpected response code for node creation request", 201, responseCode); + + hostData.clear(); + hostData.put(VirtualHost.DESIRED_STATE, State.ACTIVE); + responseCode = getRestTestHelper().submitRequest("/rest/virtualhost/" + _hostName, "PUT", hostData); + assertEquals("Unexpected response code for virtual host update status", 200, responseCode); + + waitForVirtualHostActivation(_hostName, 10000l); + + Map<String, Object> replicationNodeDetails = getRestTestHelper().getJsonAsSingletonList("/rest/replicationnode/" + _hostName + "/" + nodeName); + assertLocalNode(nodeData, replicationNodeDetails); + + // make sure that the host is saved in the broker store + restartBroker(); + + hostDetails = waitForVirtualHostActivation(_hostName, 10000l); + Asserts.assertVirtualHost(_hostName, hostDetails); + assertEquals("Unexpected virtual host type", BDBHAVirtualHostFactory.TYPE.toString(), hostDetails.get(VirtualHost.TYPE)); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> nodes = (List<Map<String, Object>>) hostDetails.get(VIRTUALHOST_NODES_ATTRIBUTE); + assertEquals("Unexpected number of nodes", 1, nodes.size()); + assertLocalNode(nodeData, nodes.get(0)); + + // verify that that node rest interface returns the same node attributes + replicationNodeDetails = getRestTestHelper().getJsonAsSingletonList("/rest/replicationnode/" + _hostName + "/" + nodeName); + assertLocalNode(nodeData, replicationNodeDetails); + } + + private void assertLocalNode(Map<String, Object> expectedNodeAttributes, Map<String, Object> actualNodesAttributes) + { + for (Map.Entry<String, Object> entry : actualNodesAttributes.entrySet()) + { + String name = entry.getKey(); + Object value = entry.getValue(); + assertEquals("Unexpected node attribute " + name + " value ", value, actualNodesAttributes.get(name)); + } + } + + private Map<String, Object> waitForVirtualHostActivation(String hostName, long timeout) throws Exception + { + Map<String, Object> hostDetails = null; + long startTime = System.currentTimeMillis(); + boolean isActive = false; + do + { + hostDetails = getRestTestHelper().getJsonAsSingletonList("/rest/virtualhost/" + hostName); + isActive = hostDetails.get(VirtualHost.STATE).equals(State.ACTIVE.name()); + Thread.sleep(100l); + } + while(!isActive && System.currentTimeMillis() - startTime < timeout ); + assertTrue("Unexpected virtual host state:" + hostDetails.get(VirtualHost.STATE), isActive); + return hostDetails; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/Broker.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/Broker.java index 66171c6fc2..bc784947fc 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/Broker.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/Broker.java @@ -31,7 +31,6 @@ import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import org.apache.qpid.server.configuration.ConfigurationEntryStore; import org.apache.qpid.server.configuration.BrokerConfigurationStoreCreator; -import org.apache.qpid.server.configuration.store.ManagementModeStoreHandler; import org.apache.qpid.server.logging.SystemOutMessageLogger; import org.apache.qpid.server.logging.actors.BrokerActor; import org.apache.qpid.server.logging.actors.CurrentActor; @@ -111,11 +110,6 @@ public class Broker ConfigurationEntryStore store = storeCreator.createStore(storeLocation, storeType, options.getInitialConfigurationLocation(), options.isOverwriteConfigurationStore(), options.getConfigProperties()); - if (options.isManagementMode()) - { - store = new ManagementModeStoreHandler(store, options); - } - _applicationRegistry = new ApplicationRegistry(store); try { diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/BrokerOptions.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/BrokerOptions.java index 316d9bd88e..14a7c77f62 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/BrokerOptions.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/BrokerOptions.java @@ -28,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.qpid.server.configuration.BrokerProperties; import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.configuration.store.MemoryConfigurationEntryStore; import org.apache.qpid.server.util.StringUtil; @@ -68,6 +69,7 @@ public class BrokerOptions private static final int MANAGEMENT_MODE_PASSWORD_LENGTH = 10; private static final File FALLBACK_WORK_DIR = new File(System.getProperty("user.dir"), "work"); + private static final int MAX_PORT_NUMBER = 65535; private String _logConfigFile; private Integer _logWatchFrequency = 0; @@ -143,6 +145,10 @@ public class BrokerOptions public void setManagementModeRmiPortOverride(int managementModeRmiPortOverride) { + if (checkLegalPortNumber(managementModeRmiPortOverride)) + { + throw new IllegalConfigurationException("Invalid rmi port is specified: " + managementModeRmiPortOverride); + } _managementModeRmiPortOverride = managementModeRmiPortOverride; } @@ -153,6 +159,10 @@ public class BrokerOptions public void setManagementModeJmxPortOverride(int managementModeJmxPortOverride) { + if (checkLegalPortNumber(managementModeJmxPortOverride)) + { + throw new IllegalConfigurationException("Invalid jmx port is specified: " + managementModeJmxPortOverride); + } _managementModeJmxPortOverride = managementModeJmxPortOverride; } @@ -163,6 +173,10 @@ public class BrokerOptions public void setManagementModeHttpPortOverride(int managementModeHttpPortOverride) { + if (checkLegalPortNumber(managementModeHttpPortOverride)) + { + throw new IllegalConfigurationException("Invalid http port is specified: " + managementModeHttpPortOverride); + } _managementModeHttpPortOverride = managementModeHttpPortOverride; } @@ -373,4 +387,11 @@ public class BrokerOptions return _configProperties.get(QPID_HOME_DIR); } + + private boolean checkLegalPortNumber(int portNumber) + { + return portNumber < 1 || portNumber > MAX_PORT_NUMBER; + } + + } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java index 189f5916e0..897c750667 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java @@ -121,11 +121,6 @@ public class VirtualHostConfiguration extends AbstractConfiguration return getLongValue("housekeeping.checkPeriod", _defaultHouseKeepingCheckPeriod); } - public Configuration getStoreConfiguration() - { - return getConfig().subset("store"); - } - public String getMessageStoreClass() { return getStringValue("store.class", null); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProvider.java index 14b6d9f118..19a6190c15 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProvider.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProvider.java @@ -33,6 +33,7 @@ import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.Plugin; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.model.adapter.AccessControlProviderFactory; @@ -126,7 +127,10 @@ public class DefaultRecovererProvider implements RecovererProvider { return new PluginRecoverer(_pluginFactoryServiceLoader); } - + else if(ReplicationNode.class.getSimpleName().equals(type)) + { + return new ReplicationNodeRecoverer(); + } return null; } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/ReplicationNodeRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/ReplicationNodeRecoverer.java new file mode 100644 index 0000000000..97ac0ced34 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/ReplicationNodeRecoverer.java @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.configuration.startup; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.plugin.ReplicationNodeFactory; + +public class ReplicationNodeRecoverer implements ConfiguredObjectRecoverer<ReplicationNode> +{ + + @Override + public ReplicationNode create(RecovererProvider recovererProvider, ConfigurationEntry entry, ConfiguredObject... parents) + { + VirtualHost virtualHost = RecovererHelper.verifyOnlyParentIsOfType(VirtualHost.class, parents); + String type = virtualHost.getType(); + ReplicationNodeFactory replicationNodeFactory = ReplicationNodeFactory.FACTORIES.get(type); + if (replicationNodeFactory == null) + { + throw new IllegalStateException("Cannot find ReplicationNodeFactory for type '" + type + "'"); + } + return replicationNodeFactory.createInstance(entry.getId() , entry.getAttributes(), virtualHost); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/VirtualHostRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/VirtualHostRecoverer.java index 4f863adfb5..c2f75246fd 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/VirtualHostRecoverer.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/VirtualHostRecoverer.java @@ -21,6 +21,9 @@ package org.apache.qpid.server.configuration.startup; +import java.util.Collection; +import java.util.Map; + import org.apache.qpid.server.configuration.ConfigurationEntry; import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; import org.apache.qpid.server.configuration.RecovererProvider; @@ -45,7 +48,21 @@ public class VirtualHostRecoverer implements ConfiguredObjectRecoverer<VirtualHo { Broker broker = RecovererHelper.verifyOnlyBrokerIsParent(parents); - return new VirtualHostAdapter(entry.getId(), entry.getAttributes(), broker, _brokerStatisticsGatherer, broker.getTaskExecutor()); + Map<String, Object> attributes = entry.getAttributes(); + VirtualHostAdapter virtualHostAdapter = new VirtualHostAdapter(entry.getId(), attributes, broker, _brokerStatisticsGatherer, broker.getTaskExecutor()); + + Map<String, Collection<ConfigurationEntry>> childEntries = entry.getChildren(); + for (Map.Entry<String, Collection<ConfigurationEntry>> childrenEntry : childEntries.entrySet()) + { + String childType = childrenEntry.getKey(); + ConfiguredObjectRecoverer<? extends ConfiguredObject> recoverer = recovererProvider.getRecoverer(childType); + for (ConfigurationEntry childEntry : childrenEntry.getValue()) + { + ConfiguredObject configuredObject = recoverer.create(recovererProvider, childEntry, virtualHostAdapter); + virtualHostAdapter.recoverChild(configuredObject); + } + } + return virtualHostAdapter; } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java deleted file mode 100644 index ab06f1b94b..0000000000 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java +++ /dev/null @@ -1,353 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.qpid.server.configuration.store; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import org.apache.log4j.Logger; -import org.apache.qpid.server.BrokerOptions; -import org.apache.qpid.server.configuration.ConfigurationEntry; -import org.apache.qpid.server.configuration.ConfigurationEntryStore; -import org.apache.qpid.server.configuration.IllegalConfigurationException; -import org.apache.qpid.server.model.Port; -import org.apache.qpid.server.model.Protocol; -import org.apache.qpid.server.model.State; -import org.apache.qpid.server.model.VirtualHost; -import org.apache.qpid.server.util.MapValueConverter; - -public class ManagementModeStoreHandler implements ConfigurationEntryStore -{ - private static final Logger LOGGER = Logger.getLogger(ManagementModeStoreHandler.class); - - private static final String MANAGEMENT_MODE_PORT_PREFIX = "MANAGEMENT-MODE-PORT-"; - private static final String PORT_TYPE = Port.class.getSimpleName(); - private static final String VIRTUAL_HOST_TYPE = VirtualHost.class.getSimpleName(); - private static final String ATTRIBUTE_STATE = VirtualHost.STATE; - private static final Object MANAGEMENT_MODE_AUTH_PROVIDER = "mm-auth"; - - - private final ConfigurationEntryStore _store; - private final Map<UUID, ConfigurationEntry> _cliEntries; - private final Map<UUID, Object> _quiescedEntries; - private final UUID _rootId; - - public ManagementModeStoreHandler(ConfigurationEntryStore store, BrokerOptions options) - { - ConfigurationEntry storeRoot = store.getRootEntry(); - _store = store; - _rootId = storeRoot.getId(); - _cliEntries = createPortsFromCommandLineOptions(options); - _quiescedEntries = quiesceEntries(storeRoot, options); - } - - @Override - public ConfigurationEntry getRootEntry() - { - return getEntry(_rootId); - } - - @Override - public ConfigurationEntry getEntry(UUID id) - { - synchronized (_store) - { - if (_cliEntries.containsKey(id)) - { - return _cliEntries.get(id); - } - - ConfigurationEntry entry = _store.getEntry(id); - if (_quiescedEntries.containsKey(id)) - { - entry = createEntryWithState(entry, State.QUIESCED); - } - else if (id == _rootId) - { - entry = createRootWithCLIEntries(entry); - } - return entry; - } - } - - @Override - public void save(ConfigurationEntry... entries) - { - synchronized (_store) - { - ConfigurationEntry[] entriesToSave = new ConfigurationEntry[entries.length]; - - for (int i = 0; i < entries.length; i++) - { - ConfigurationEntry entry = entries[i]; - UUID id = entry.getId(); - if (_cliEntries.containsKey(id)) - { - throw new IllegalConfigurationException("Cannot save configuration provided as command line argument:" - + entry); - } - else if (_quiescedEntries.containsKey(id)) - { - // save entry with the original state - entry = createEntryWithState(entry, _quiescedEntries.get(ATTRIBUTE_STATE)); - } - else if (_rootId.equals(id)) - { - // save root without command line entries - Set<UUID> childrenIds = new HashSet<UUID>(entry.getChildrenIds()); - if (!_cliEntries.isEmpty()) - { - childrenIds.removeAll(_cliEntries.entrySet()); - } - HashMap<String, Object> attributes = new HashMap<String, Object>(entry.getAttributes()); - entry = new ConfigurationEntry(entry.getId(), entry.getType(), attributes, childrenIds, this); - } - entriesToSave[i] = entry; - } - - _store.save(entriesToSave); - } - } - - @Override - public UUID[] remove(UUID... entryIds) - { - synchronized (_store) - { - for (UUID id : entryIds) - { - if (_cliEntries.containsKey(id)) - { - throw new IllegalConfigurationException("Cannot change configuration for command line entry:" - + _cliEntries.get(id)); - } - } - UUID[] result = _store.remove(entryIds); - for (UUID id : entryIds) - { - if (_quiescedEntries.containsKey(id)) - { - _quiescedEntries.remove(id); - } - } - return result; - } - } - - @Override - public void copyTo(String copyLocation) - { - synchronized (_store) - { - _store.copyTo(copyLocation); - } - } - - @Override - public String getStoreLocation() - { - return _store.getStoreLocation(); - } - - @Override - public int getVersion() - { - return _store.getVersion(); - } - - @Override - public String getType() - { - return _store.getType(); - } - - private Map<UUID, ConfigurationEntry> createPortsFromCommandLineOptions(BrokerOptions options) - { - int managementModeRmiPortOverride = options.getManagementModeRmiPortOverride(); - if (managementModeRmiPortOverride < 0) - { - throw new IllegalConfigurationException("Invalid rmi port is specified: " + managementModeRmiPortOverride); - } - int managementModeJmxPortOverride = options.getManagementModeJmxPortOverride(); - if (managementModeJmxPortOverride < 0) - { - throw new IllegalConfigurationException("Invalid jmx port is specified: " + managementModeJmxPortOverride); - } - int managementModeHttpPortOverride = options.getManagementModeHttpPortOverride(); - if (managementModeHttpPortOverride < 0) - { - throw new IllegalConfigurationException("Invalid http port is specified: " + managementModeHttpPortOverride); - } - Map<UUID, ConfigurationEntry> cliEntries = new HashMap<UUID, ConfigurationEntry>(); - if (managementModeRmiPortOverride != 0) - { - ConfigurationEntry entry = createCLIPortEntry(managementModeRmiPortOverride, Protocol.RMI); - cliEntries.put(entry.getId(), entry); - if (managementModeJmxPortOverride == 0) - { - ConfigurationEntry connectorEntry = createCLIPortEntry(managementModeRmiPortOverride + 100, Protocol.JMX_RMI); - cliEntries.put(connectorEntry.getId(), connectorEntry); - } - } - if (managementModeJmxPortOverride != 0) - { - ConfigurationEntry entry = createCLIPortEntry(managementModeJmxPortOverride, Protocol.JMX_RMI); - cliEntries.put(entry.getId(), entry); - } - if (managementModeHttpPortOverride != 0) - { - ConfigurationEntry entry = createCLIPortEntry(managementModeHttpPortOverride, Protocol.HTTP); - cliEntries.put(entry.getId(), entry); - } - return cliEntries; - } - - private ConfigurationEntry createCLIPortEntry(int port, Protocol protocol) - { - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Port.PORT, port); - attributes.put(Port.PROTOCOLS, Collections.singleton(protocol)); - attributes.put(Port.NAME, MANAGEMENT_MODE_PORT_PREFIX + protocol.name()); - if (protocol != Protocol.RMI) - { - attributes.put(Port.AUTHENTICATION_PROVIDER, MANAGEMENT_MODE_AUTH_PROVIDER); - } - ConfigurationEntry portEntry = new ConfigurationEntry(UUID.randomUUID(), PORT_TYPE, attributes, - Collections.<UUID> emptySet(), this); - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Add management mode port configuration " + portEntry + " for port " + port + " and protocol " - + protocol); - } - return portEntry; - } - - private ConfigurationEntry createRootWithCLIEntries(ConfigurationEntry storeRoot) - { - Set<UUID> childrenIds = new HashSet<UUID>(storeRoot.getChildrenIds()); - if (!_cliEntries.isEmpty()) - { - childrenIds.addAll(_cliEntries.keySet()); - } - ConfigurationEntry root = new ConfigurationEntry(storeRoot.getId(), storeRoot.getType(), new HashMap<String, Object>( - storeRoot.getAttributes()), childrenIds, this); - return root; - } - - private Map<UUID, Object> quiesceEntries(ConfigurationEntry storeRoot, BrokerOptions options) - { - Map<UUID, Object> quiescedEntries = new HashMap<UUID, Object>(); - Set<UUID> childrenIds; - int managementModeRmiPortOverride = options.getManagementModeRmiPortOverride(); - int managementModeJmxPortOverride = options.getManagementModeJmxPortOverride(); - int managementModeHttpPortOverride = options.getManagementModeHttpPortOverride(); - childrenIds = storeRoot.getChildrenIds(); - for (UUID id : childrenIds) - { - ConfigurationEntry entry = _store.getEntry(id); - String entryType = entry.getType(); - Map<String, Object> attributes = entry.getAttributes(); - boolean quiesce = false; - if (VIRTUAL_HOST_TYPE.equals(entryType) && options.isManagementModeQuiesceVirtualHosts()) - { - quiesce = true; - } - else if (PORT_TYPE.equals(entryType)) - { - if (attributes == null) - { - throw new IllegalConfigurationException("Port attributes are not set in " + entry); - } - Set<Protocol> protocols = getPortProtocolsAttribute(attributes); - if (protocols == null) - { - quiesce = true; - } - else - { - for (Protocol protocol : protocols) - { - switch (protocol) - { - case JMX_RMI: - quiesce = managementModeJmxPortOverride > 0 || managementModeRmiPortOverride > 0; - break; - case RMI: - quiesce = managementModeRmiPortOverride > 0; - break; - case HTTP: - quiesce = managementModeHttpPortOverride > 0; - break; - default: - quiesce = true; - } - } - } - } - if (quiesce) - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug("Management mode quiescing entry " + entry); - } - - // save original state - quiescedEntries.put(entry.getId(), attributes.get(ATTRIBUTE_STATE)); - } - } - return quiescedEntries; - } - - private Set<Protocol> getPortProtocolsAttribute(Map<String, Object> attributes) - { - Object object = attributes.get(Port.PROTOCOLS); - if (object == null) - { - return null; - } - return MapValueConverter.getEnumSetAttribute(Port.PROTOCOLS, attributes, Protocol.class); - } - - private ConfigurationEntry createEntryWithState(ConfigurationEntry entry, Object state) - { - Map<String, Object> attributes = new HashMap<String, Object>(entry.getAttributes()); - if (state == null) - { - attributes.remove(ATTRIBUTE_STATE); - } - else - { - attributes.put(ATTRIBUTE_STATE, state); - } - Set<UUID> originalChildren = entry.getChildrenIds(); - Set<UUID> children = null; - if (originalChildren != null) - { - children = new HashSet<UUID>(originalChildren); - } - return new ConfigurationEntry(entry.getId(), entry.getType(), attributes, children, entry.getStore()); - } - -} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java index a04df9efe4..cee1232fb6 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java @@ -547,7 +547,7 @@ public class MemoryConfigurationEntryStore implements ConfigurationEntryStore } else if (fieldNode.isObject()) { - // ignore, in-line objects are not supported yet + attributes.put(fieldName, toObject(fieldNode) ); } else { diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListener.java index 6cd3447ebd..bef4b6dec9 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListener.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListener.java @@ -35,6 +35,7 @@ import org.apache.qpid.server.model.ConfigurationChangeListener; import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.model.Model; import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; @@ -61,8 +62,9 @@ public class StoreConfigurationChangeListener implements ConfigurationChangeList @Override public void childAdded(ConfiguredObject object, ConfiguredObject child) { - // exclude VirtualHost children from storing in broker store - if (!(object instanceof VirtualHost)) + // exclude VirtualHost children (except for local ReplicationNode) from storing in broker stores + if (!(object instanceof VirtualHost) || (object instanceof VirtualHost && child instanceof ReplicationNode + && ((ReplicationNode) child).isLocal())) { child.addChangeListener(this); ConfigurationEntry parentEntry = toConfigurationEntry(object); @@ -95,10 +97,10 @@ public class StoreConfigurationChangeListener implements ConfigurationChangeList private Set<UUID> getChildrenIds(ConfiguredObject object, Class<? extends ConfiguredObject> objectType) { - // Virtual Host children's IDs should not be stored in broker store + // Virtual Host children's IDs (except local replication node) should not be stored in broker store if (object instanceof VirtualHost) { - return Collections.emptySet(); + return getVirtualHostStorableChildrenIds((VirtualHost)object); } Set<UUID> childrenIds = new TreeSet<UUID>(); Collection<Class<? extends ConfiguredObject>> childClasses = Model.getInstance().getChildTypes(objectType); @@ -119,6 +121,32 @@ public class StoreConfigurationChangeListener implements ConfigurationChangeList return childrenIds; } + private Set<UUID> getVirtualHostStorableChildrenIds(VirtualHost host) + { + Collection<ReplicationNode> nodes = host.getChildren(ReplicationNode.class); + if (nodes.isEmpty()) + { + return Collections.emptySet(); + } + else + { + ReplicationNode localNode = null; + for (ReplicationNode node : nodes) + { + if (node.isLocal()) + { + localNode = node; + break; + } + } + if (localNode == null) + { + throw new IllegalStateException("Cannot find local replication node among virtual host nodes"); + } + return Collections.singleton(localNode.getId()); + } + } + private Class<? extends ConfiguredObject> getConfiguredObjectType(ConfiguredObject object) { if (object instanceof Broker) diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/internal/InternalMessage.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/internal/InternalMessage.java index f972bd78f6..1584cf3029 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/internal/InternalMessage.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/internal/InternalMessage.java @@ -20,6 +20,7 @@ */ package org.apache.qpid.server.message.internal; +import org.apache.qpid.AMQStoreException; import org.apache.qpid.server.message.AMQMessageHeader; import org.apache.qpid.server.message.AbstractServerMessageImpl; import org.apache.qpid.server.store.MessageStore; @@ -140,6 +141,10 @@ public class InternalMessage extends AbstractServerMessageImpl<InternalMessage, return new InternalMessage(handle, internalHeader, bodyObject); } + catch(AMQStoreException e) + { + throw new RuntimeException(e); + } catch (IOException e) { throw new RuntimeException(e); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java index 5df6d9475e..28881ff9a6 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java @@ -185,4 +185,9 @@ public interface Broker extends ConfiguredObject boolean isManagementMode(); AuthenticationProvider getAuthenticationProvider(SocketAddress localAddress); + + /** + * TODO: Remove this + */ + boolean isPreviouslyUsedPortNumber(int port); } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObject.java index ab9c60573a..3bd9441f87 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObject.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObject.java @@ -38,6 +38,8 @@ import java.util.UUID; */ public interface ConfiguredObject { + final String DESIRED_STATE = "desiredState"; + public static final String ID = "id"; public static final String NAME = "name"; // public static final String TYPE = "type"; @@ -269,4 +271,19 @@ public interface ConfiguredObject ConfiguredObject... otherParents); void setAttributes(Map<String, Object> attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException; + + /** + * Tells the object (and all its children) to attain its desired state. The desired state will come from the store (if available) + * or default to {@link State#ACTIVE} otherwise. + * <p> + * This method is called exactly once immediately after recovery on broker startup or object creation to initialize the object and its children. + */ + void attainDesiredState(); + + /** + * Close the object. + * <p> + * The method is used to deinitialize the configured objects on broker shutdown or object is deleted. + */ + void close(); } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Model.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Model.java index 50538a5580..255193c56e 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Model.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Model.java @@ -37,7 +37,7 @@ public class Model * */ public static final int MODEL_MAJOR_VERSION = 1; - public static final int MODEL_MINOR_VERSION = 2; + public static final int MODEL_MINOR_VERSION = 3; public static final String MODEL_VERSION = MODEL_MAJOR_VERSION + "." + MODEL_MINOR_VERSION; private static final Model MODEL_INSTANCE = new Model(); @@ -68,6 +68,7 @@ public class Model addRelationship(VirtualHost.class, Queue.class); addRelationship(VirtualHost.class, Connection.class); addRelationship(VirtualHost.class, VirtualHostAlias.class); + addRelationship(VirtualHost.class, ReplicationNode.class); addRelationship(AuthenticationProvider.class, User.class); addRelationship(AuthenticationProvider.class, PreferencesProvider.class); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ReplicationNode.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ReplicationNode.java new file mode 100644 index 0000000000..0771908a5a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ReplicationNode.java @@ -0,0 +1,109 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface ReplicationNode extends ConfiguredObject +{ + String STATE = "state"; + String CREATED = "created"; + String DURABLE = "durable"; + String LIFETIME_POLICY = "lifetimePolicy"; + String TIME_TO_LIVE = "timeToLive"; + String TYPE = "type"; + String UPDATED = "updated"; + + /** Name of the group to which this replication node belongs */ + String GROUP_NAME = "groupName"; + + /** Node host name/IP and port separated by semicolon*/ + String HOST_PORT = "hostPort"; + + /** Node helper host name/IP and port separated by semicolon*/ + String HELPER_HOST_PORT = "helperHostPort"; + + /** Durability settings*/ + String DURABILITY = "durability"; + + /** Sync multiple transactions on disc at the same time*/ + String COALESCING_SYNC = "coalescingSync"; + + /** A designated primary setting for 2-nodes group*/ + String DESIGNATED_PRIMARY = "designatedPrimary"; + + /** Node priority. 1 signifies normal priority; 0 signifies node will never be elected. */ + String PRIORITY = "priority"; + + /** The overridden minimum number of group nodes required to commit transaction on this node instead of simple majority*/ + String QUORUM_OVERRIDE = "quorumOverride"; + + /** Node role: MASTER,REPLICA,UNKNOWN,DETACHED*/ + String ROLE = "role"; + + /** Time when node joined the group */ + String JOIN_TIME = "joinTime"; + + /** Last known replication transaction id */ + String LAST_KNOWN_REPLICATION_TRANSACTION_ID= "lastKnownReplicationTransactionId"; + + /** Map with additional implementation specific node settings */ + String PARAMETERS = "parameters"; + + /** Map with additional implementation specific replication parameters*/ + String REPLICATION_PARAMETERS = "replicationParameters"; + + /** Store path */ + String STORE_PATH = "storePath"; + + // Attributes + public static final Collection<String> AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + TYPE, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + GROUP_NAME, + HOST_PORT, + HELPER_HOST_PORT, + DURABILITY, + COALESCING_SYNC, + DESIGNATED_PRIMARY, + PRIORITY, + QUORUM_OVERRIDE, + ROLE, + JOIN_TIME, + LAST_KNOWN_REPLICATION_TRANSACTION_ID, + PARAMETERS, + REPLICATION_PARAMETERS, + STORE_PATH + )); + + public boolean isLocal(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/State.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/State.java index a4287e09b1..a30d488054 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/State.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/State.java @@ -27,6 +27,6 @@ public enum State STOPPED, ACTIVE, DELETED, - REPLICA, + UNAVAILABLE, ERRORED } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java index 7def89025d..2af644518a 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java @@ -97,4 +97,9 @@ public class UUIDGenerator { return createUUID(PreferencesProvider.class.getName(), authenticationProviderName, preferencesProviderName); } + + public static UUID generateReplicationNodeId(String groupName, String nodeName) + { + return createUUID(ReplicationNode.class.getName(), groupName, nodeName); + } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHost.java index f0241f8b30..81ceb88c79 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHost.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHost.java @@ -20,6 +20,7 @@ */ package org.apache.qpid.server.model; +import org.apache.qpid.server.configuration.updater.TaskExecutor; import org.apache.qpid.server.message.MessageInstance; import org.apache.qpid.server.queue.QueueEntry; import org.apache.qpid.server.security.SecurityManager; @@ -125,6 +126,9 @@ public interface VirtualHost extends ConfiguredObject String UPDATED = "updated"; String CONFIG_PATH = "configPath"; + String QUIESCE_ON_MASTER_CHANGE = "quiesceOnMasterChange"; + String REMOTE_REPLICATION_NODE_MONITOR_INTERVAL = "remoteReplicationNodeMonitorInterval"; + // Attributes public static final Collection<String> AVAILABLE_ATTRIBUTES = Collections.unmodifiableList( @@ -133,6 +137,7 @@ public interface VirtualHost extends ConfiguredObject NAME, TYPE, STATE, + DESIRED_STATE, DURABLE, LIFETIME_POLICY, TIME_TO_LIVE, @@ -158,7 +163,9 @@ public interface VirtualHost extends ConfiguredObject QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE, QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, - CONFIG_PATH)); + CONFIG_PATH, + QUIESCE_ON_MASTER_CHANGE, + REMOTE_REPLICATION_NODE_MONITOR_INTERVAL)); int CURRENT_CONFIG_VERSION = 3; @@ -167,6 +174,7 @@ public interface VirtualHost extends ConfiguredObject Collection<Connection> getConnections(); Collection<Queue> getQueues(); Collection<Exchange> getExchanges(); + Collection<ReplicationNode> getReplicationNodes(); Exchange createExchange(String name, State initialState, boolean durable, LifetimePolicy lifetime, long ttl, String type, Map<String, Object> attributes) @@ -201,7 +209,11 @@ public interface VirtualHost extends ConfiguredObject */ SecurityManager getSecurityManager(); + //TODO: remove this unused method MessageStore getMessageStore(); String getType(); + + TaskExecutor getTaskExecutor(); + } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java index 7cc88f8743..5236411250 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java @@ -32,6 +32,7 @@ import org.apache.qpid.server.model.ConfigurationChangeListener; import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.model.IllegalStateTransitionException; import org.apache.qpid.server.model.State; +import org.apache.qpid.server.util.MapValueConverter; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.configuration.updater.ChangeAttributesTask; import org.apache.qpid.server.configuration.updater.ChangeStateTask; @@ -91,6 +92,8 @@ public abstract class AbstractAdapter implements ConfiguredObject } } } + + _defaultAttributes.put(DESIRED_STATE, State.ACTIVE); if (defaults != null) { _defaultAttributes.putAll(defaults); @@ -109,7 +112,7 @@ public abstract class AbstractAdapter implements ConfiguredObject public State getDesiredState() { - return null; //TODO + return MapValueConverter.toEnum(DESIRED_STATE, getAttribute(DESIRED_STATE), State.class); } @Override @@ -474,4 +477,15 @@ public abstract class AbstractAdapter implements ConfiguredObject return merged; } + + @Override + public void attainDesiredState() + { + setDesiredState(getActualState(), getDesiredState()); + } + + @Override + public void close() + { + } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderAdapter.java index a6fe191523..0264529fc1 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderAdapter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderAdapter.java @@ -44,9 +44,10 @@ import org.apache.qpid.server.util.MapValueConverter; public class AccessControlProviderAdapter extends AbstractAdapter implements AccessControlProvider { + private static final Logger LOGGER = Logger.getLogger(AccessControlProviderAdapter.class); - protected AccessControl _accessControl; + protected final AccessControl _accessControl; protected final Broker _broker; protected Collection<String> _supportedAttributes; @@ -210,6 +211,7 @@ public class AccessControlProviderAdapter extends AbstractAdapter implements Acc if(desiredState == State.DELETED) { + _accessControl.close(); return _state.compareAndSet(state, State.DELETED); } else if (desiredState == State.QUIESCED) @@ -297,4 +299,16 @@ public class AccessControlProviderAdapter extends AbstractAdapter implements Acc { return _accessControl; } + + @Override + public void close() + { + _accessControl.close(); + } + + @Override + public void attainDesiredState() + { + setDesiredState(State.INITIALISING, _broker.isManagementMode() ? State.QUIESCED : getDesiredState()); + } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java index c255ce2f4a..eb3432c852 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java @@ -29,9 +29,9 @@ import java.util.UUID; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; - import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; + import org.apache.qpid.server.configuration.BrokerProperties; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.logging.actors.CurrentActor; @@ -200,4 +200,22 @@ public class AmqpPortAdapter extends PortAdapter } return null; } + + @Override + protected boolean shouldQuiesce() + { + //TODO: it seems unnecessary to quiesce previously used AMQP ports as they should be closed properly + if ( _broker.isPreviouslyUsedPortNumber(getPort())) + { + // always quiesce port with port number which was previously used + return true; + } + + if (_broker.isManagementMode()) + { + return true; + } + + return false; + } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java index 033429980f..83d51cee8c 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java @@ -266,10 +266,6 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana try { _authManager.initialise(); - if (_preferencesProvider != null) - { - _preferencesProvider.setDesiredState(_preferencesProvider.getActualState(), State.ACTIVE); - } return true; } catch(RuntimeException e) @@ -302,10 +298,6 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana if (_state.compareAndSet(state, State.STOPPED)) { _authManager.close(); - if (_preferencesProvider != null) - { - _preferencesProvider.setDesiredState(_preferencesProvider.getActualState(), State.STOPPED); - } return true; } else @@ -442,13 +434,38 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana PreferencesProviderFactory factory = PreferencesProviderFactory.FACTORIES.get(type); UUID id = UUIDGenerator.generatePreferencesProviderUUID(name, getName()); PreferencesProvider pp = factory.createInstance(id, attributes, this); - pp.setDesiredState(State.INITIALISING, State.ACTIVE); + pp.attainDesiredState(); + // TODO if preferencesProvider already exists, it should be closed. _preferencesProvider = pp; return (C)pp; } throw new IllegalArgumentException("Cannot create child of class " + childClass.getSimpleName()); } + @Override + public void attainDesiredState() + { + if (_preferencesProvider != null) + { + _preferencesProvider.attainDesiredState(); + } + + super.attainDesiredState(); + } + + @Override + public void close() + { + if (_preferencesProvider != null) + { + _preferencesProvider.close(); + } + if (_authManager != null) + { + _authManager.close(); + } + } + public static class SimpleAuthenticationProviderAdapter extends AuthenticationProviderAdapter<AuthenticationManager> { diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java index b0ef11bc1b..8bc6ae63cf 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import org.apache.log4j.Logger; import org.apache.qpid.common.QpidProperties; @@ -54,6 +55,7 @@ import org.apache.qpid.server.model.LifetimePolicy; import org.apache.qpid.server.model.Model; import org.apache.qpid.server.model.Plugin; import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.Statistics; import org.apache.qpid.server.model.TrustStore; @@ -182,6 +184,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat private final AccessControlProviderFactory _accessControlProviderFactory; private final PortFactory _portFactory; private final SecurityManager _securityManager; + private final AtomicReference<State> _state; private final Collection<String> _supportedVirtualHostStoreTypes; private Collection<String> _supportedBrokerStoreTypes; @@ -210,15 +213,57 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat _supportedVirtualHostStoreTypes = new MessageStoreCreator().getStoreTypes(); _supportedBrokerStoreTypes = new BrokerConfigurationStoreCreator().getStoreTypes(); _brokerStore = brokerStore; + + _state = new AtomicReference<State>(State.INITIALISING); if (_brokerOptions.isManagementMode()) { - AuthenticationManager authManager = new SimpleAuthenticationManager(BrokerOptions.MANAGEMENT_MODE_USER_NAME, _brokerOptions.getManagementModePassword()); - AuthenticationProvider authenticationProvider = new SimpleAuthenticationProviderAdapter(UUID.randomUUID(), this, - authManager, Collections.<String, Object> emptyMap(), Collections.<String> emptySet()); - _managementAuthenticationProvider = authenticationProvider; + createManagementModeConfiguredObjects(); + } + } + + private void createManagementModeConfiguredObjects() + { + AuthenticationManager authManager = new SimpleAuthenticationManager(BrokerOptions.MANAGEMENT_MODE_USER_NAME, _brokerOptions.getManagementModePassword()); + AuthenticationProvider authenticationProvider = new SimpleAuthenticationProviderAdapter(UUID.randomUUID(), this, + authManager, Collections.<String, Object> emptyMap(), Collections.<String> emptySet()); + _managementAuthenticationProvider = authenticationProvider; + + int httpPortOverride = _brokerOptions.getManagementModeHttpPortOverride(); + if (httpPortOverride != 0) + { + createPort(createCLIPortAttributes(httpPortOverride, Protocol.HTTP)); + } + + int managementModeRmiPortOverride = _brokerOptions.getManagementModeRmiPortOverride(); + int managementModeJmxPortOverride = _brokerOptions.getManagementModeJmxPortOverride(); + if (managementModeRmiPortOverride != 0) + { + createPort(createCLIPortAttributes(managementModeRmiPortOverride, Protocol.RMI)); + if (managementModeJmxPortOverride == 0) + { + createPort(createCLIPortAttributes(managementModeRmiPortOverride + 100, Protocol.JMX_RMI)); + } + } + if (managementModeJmxPortOverride != 0) + { + createPort(createCLIPortAttributes(managementModeJmxPortOverride, Protocol.JMX_RMI)); } } + private Map<String, Object> createCLIPortAttributes(int port, Protocol protocol) + { + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(Port.PORT, port); + attributes.put(Port.PROTOCOLS, Collections.singleton(protocol)); + attributes.put(Port.NAME, PortAdapter.MANAGEMENT_MODE_PORT_PREFIX + protocol.name()); + if (protocol != Protocol.RMI) + { + //TODO: analyze whether setting of auth provider is still needed + //attributes.put(Port.AUTHENTICATION_PROVIDER, MANAGEMENT_MODE_AUTH_PROVIDER); + } + return attributes; + } + public Collection<VirtualHost> getVirtualHosts() { synchronized(_vhostAdapters) @@ -291,6 +336,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat private VirtualHost createVirtualHost(final Map<String, Object> attributes) throws AccessControlException, IllegalArgumentException { + final VirtualHostAdapter virtualHostAdapter = new VirtualHostAdapter(UUID.randomUUID(), attributes, this, _statisticsGatherer, getTaskExecutor()); addVirtualHost(virtualHostAdapter); @@ -300,7 +346,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat SecurityManager.setAccessChecksDisabled(true); try { - virtualHostAdapter.setDesiredState(State.INITIALISING, State.ACTIVE); + virtualHostAdapter.attainDesiredState(); } finally { @@ -333,7 +379,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat public State getActualState() { - return null; //TODO + return _state.get(); } @@ -460,16 +506,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat { Port port = _portFactory.createPort(UUID.randomUUID(), this, attributes); addPort(port); - - //1. AMQP ports are disabled during ManagementMode. - //2. The management plugins can currently only start ports at broker startup and - // not when they are newly created via the management interfaces. - //3. When active ports are deleted, or their port numbers updated, the broker must be - // restarted for it to take effect so we can't reuse port numbers until it is. - boolean quiesce = isManagementMode() || !(port instanceof AmqpPortAdapter) || isPreviouslyUsedPortNumber(port); - - port.setDesiredState(State.INITIALISING, quiesce ? State.QUIESCED : State.ACTIVE); - + port.attainDesiredState(); return port; } @@ -513,9 +550,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat addAccessControlProvider(accessControlProvider); } - boolean quiesce = isManagementMode() ; - accessControlProvider.setDesiredState(State.INITIALISING, quiesce ? State.QUIESCED : State.ACTIVE); - + accessControlProvider.attainDesiredState(); return accessControlProvider; } @@ -565,7 +600,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat private AuthenticationProvider createAuthenticationProvider(Map<String, Object> attributes) { AuthenticationProvider authenticationProvider = _authenticationProviderFactory.create(UUID.randomUUID(), this, attributes); - authenticationProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + authenticationProvider.attainDesiredState(); addAuthenticationProvider(authenticationProvider); return authenticationProvider; } @@ -597,7 +632,7 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat private GroupProvider createGroupProvider(Map<String, Object> attributes) { GroupProvider groupProvider = _groupProviderFactory.create(UUID.randomUUID(), this, attributes); - groupProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + groupProvider.attainDesiredState(); addGroupProvider(groupProvider); return groupProvider; } @@ -861,75 +896,17 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat @Override public boolean setState(State currentState, State desiredState) { - if (desiredState == State.ACTIVE) + if ((desiredState == State.ACTIVE || desiredState == State.STOPPED) && _state.compareAndSet(getActualState(), desiredState)) { - changeState(_groupProviders, currentState, State.ACTIVE, false); - changeState(_authenticationProviders, currentState, State.ACTIVE, false); - changeState(_accessControlProviders, currentState, State.ACTIVE, false); - - CurrentActor.set(new BrokerActor(getRootMessageLogger())); - try - { - changeState(_vhostAdapters, currentState, State.ACTIVE, false); - } - finally - { - CurrentActor.remove(); - } - - changeState(_portAdapters, currentState,State.ACTIVE, false); - changeState(_plugins, currentState,State.ACTIVE, false); - - if (isManagementMode()) - { - CurrentActor.get().message(BrokerMessages.MANAGEMENT_MODE(BrokerOptions.MANAGEMENT_MODE_USER_NAME, _brokerOptions.getManagementModePassword())); - } - return true; - } - else if (desiredState == State.STOPPED) - { - changeState(_plugins, currentState,State.STOPPED, true); - changeState(_portAdapters, currentState, State.STOPPED, true); - changeState(_vhostAdapters,currentState, State.STOPPED, true); - changeState(_authenticationProviders, currentState, State.STOPPED, true); - changeState(_groupProviders, currentState, State.STOPPED, true); return true; } return false; } - private void changeState(Map<?, ? extends ConfiguredObject> configuredObjectMap, State currentState, State desiredState, boolean swallowException) + @Override + public State getDesiredState() { - synchronized(configuredObjectMap) - { - Collection<? extends ConfiguredObject> adapters = configuredObjectMap.values(); - for (ConfiguredObject configuredObject : adapters) - { - if (State.ACTIVE.equals(desiredState) && State.QUIESCED.equals(configuredObject.getActualState())) - { - if (LOGGER.isDebugEnabled()) - { - LOGGER.debug(configuredObject + " cannot be activated as it is " +State.QUIESCED); - } - continue; - } - try - { - configuredObject.setDesiredState(currentState, desiredState); - } - catch(RuntimeException e) - { - if (swallowException) - { - LOGGER.error("Failed to stop " + configuredObject, e); - } - else - { - throw e; - } - } - } - } + return State.ACTIVE; } @Override @@ -1258,8 +1235,70 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat } } - private boolean isPreviouslyUsedPortNumber(Port port) + @Override + public boolean isPreviouslyUsedPortNumber(int port) + { + return _stillInUsePortNumbers.containsValue(port); + } + + @Override + public void attainDesiredState() + { + attainDesiredState(_groupProviders.values()); + attainDesiredState(_authenticationProviders.values()); + attainDesiredState(_accessControlProviders.values()); + CurrentActor.set(new BrokerActor(getRootMessageLogger())); + try + { + attainDesiredState(_vhostAdapters.values()); + } + finally + { + CurrentActor.remove(); + } + attainDesiredState(_portAdapters.values()); + attainDesiredState(_plugins.values()); + + if (isManagementMode()) + { + CurrentActor.get().message(BrokerMessages.MANAGEMENT_MODE(BrokerOptions.MANAGEMENT_MODE_USER_NAME, _brokerOptions.getManagementModePassword())); + } + + super.attainDesiredState(); + } + + private <T extends ConfiguredObject> void attainDesiredState(Collection<T> children) { - return _stillInUsePortNumbers.containsValue(port.getPort()); + for (T child : children) + { + child.attainDesiredState(); + } } + + @Override + public void close() + { + closeAll(_plugins.values()); + closeAll(_portAdapters.values()); + closeAll(_vhostAdapters.values()); + closeAll(_authenticationProviders.values()); + closeAll(_groupProviders.values()); + closeAll(_accessControlProviders.values()); + } + + private <T extends ConfiguredObject> void closeAll(Collection<T> children) + { + for (T child : children) + { + try + { + child.close(); + } + catch(Exception e) + { + LOGGER.warn("Failed to close " + child, e); + } + } + } + } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProvider.java index 39f0017fc3..4e6952fff5 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProvider.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProvider.java @@ -227,6 +227,8 @@ public class FileSystemPreferencesProvider extends AbstractAdapter implements Pr finally { _store.delete(); + // TODO this should change to that the authentication provider listens for the delete and + // responds accordingly. _authenticationProvider.setPreferencesProvider(null); } return true; @@ -389,6 +391,13 @@ public class FileSystemPreferencesProvider extends AbstractAdapter implements Pr _store.createIfNotExist(); } + @Override + public void close() + { + _store.close(); + } + + public static class FileSystemPreferencesStore { private final ObjectMapper _objectMapper; diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java index 9323606c83..0855fb289e 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java @@ -378,6 +378,19 @@ public class GroupProviderAdapter extends AbstractAdapter implements throw new UnsupportedOperationException("Changing attributes on group providers is not supported."); } + + @Override + public State getDesiredState() + { + return State.ACTIVE; + } + + @Override + public void close() + { + _groupManager.close(); + } + private class GroupAdapter extends AbstractAdapter implements Group { private final String _group; @@ -706,5 +719,4 @@ public class GroupProviderAdapter extends AbstractAdapter implements } } - } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java index 882335626d..d64a50ffca 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java @@ -26,6 +26,7 @@ import java.security.AccessControlException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,9 +34,11 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; +import org.apache.log4j.Logger; import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.ConfiguredObjectFinder; import org.apache.qpid.server.model.Connection; import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.LifetimePolicy; @@ -55,6 +58,8 @@ import org.apache.qpid.server.configuration.updater.TaskExecutor; public class PortAdapter extends AbstractAdapter implements Port { + private static final Logger LOGGER = Logger.getLogger(PortAdapter.class); + @SuppressWarnings("serial") public static final Map<String, Type> ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap<String, Type>(){{ put(NAME, String.class); @@ -73,6 +78,8 @@ public class PortAdapter extends AbstractAdapter implements Port put(AUTHENTICATION_PROVIDER, String.class); }}); + static final String MANAGEMENT_MODE_PORT_PREFIX = "MANAGEMENT-MODE-PORT-"; + private final Broker _broker; private AuthenticationProvider _authenticationProvider; private AtomicReference<State> _state; @@ -81,7 +88,6 @@ public class PortAdapter extends AbstractAdapter implements Port { super(id, defaults, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES), taskExecutor); _broker = broker; - State state = MapValueConverter.getEnumAttribute(State.class, STATE, attributes, State.INITIALISING); Collection<Protocol> protocols = getProtocols(); boolean rmiRegistry = protocols != null && protocols.contains(Protocol.RMI); @@ -100,8 +106,9 @@ public class PortAdapter extends AbstractAdapter implements Port } } - _state = new AtomicReference<State>(state); + _state = new AtomicReference<State>(State.INITIALISING); addParent(Broker.class, broker); + } @Override @@ -564,7 +571,61 @@ public class PortAdapter extends AbstractAdapter implements Port @Override public String toString() { - return getClass().getSimpleName() + " [id=" + getId() + ", name=" + getName() + ", port=" + getPort() + "]"; + return getClass().getSimpleName() + " [id=" + getId() + ", name=" + getName() + ", port=" + getPort() + ", state=" + getActualState() + "]"; + } + + @Override + public void close() + { + onStop(); + } + + @Override + public void attainDesiredState() + { + State desiredState = shouldQuiesce() ? State.QUIESCED : getDesiredState(); + setDesiredState(getActualState(), desiredState); } + protected boolean shouldQuiesce() + { + if (getName().startsWith(MANAGEMENT_MODE_PORT_PREFIX)) + { + // port is CLI port therefore it should be active + return false; + } + + if ( _broker.isPreviouslyUsedPortNumber(getPort())) + { + // always quiesce port with port number which was previously used + return true; + } + + if (_broker.isManagementMode()) + { + EnumSet<Protocol> managementProtocols = EnumSet.of(Protocol.HTTP, Protocol.JMX_RMI, Protocol.RMI); + Collection<Port> ports = _broker.getPorts(); + Collection<Protocol> protocols = getProtocols(); + for (Protocol protocol : protocols) + { + Port managementModePort = ConfiguredObjectFinder.findConfiguredObjectByName(ports, MANAGEMENT_MODE_PORT_PREFIX + protocol.name()); + if (managementProtocols.contains(protocol) && managementModePort != null) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Port " + this + " is overridden by management port " + managementModePort); + } + + // quiesce the port if exists overridden CLI port for the protocol + return true; + } + } + } + else + { + // quiesce non-amqp ports created after broker start-up + return _broker.getActualState() == State.ACTIVE; + } + return false; + } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java index c43dc34d2f..9016c2ac66 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java @@ -20,6 +20,8 @@ */ package org.apache.qpid.server.model.adapter; +import static org.apache.qpid.server.model.VirtualHost.ID; + import java.io.File; import java.lang.reflect.Type; import java.security.AccessControlException; @@ -34,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.ConfigurationException; @@ -44,6 +47,9 @@ import org.apache.qpid.AMQException; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.configuration.VirtualHostConfiguration; import org.apache.qpid.server.configuration.XmlConfigurationUtilities.MyConfiguration; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.logging.actors.BrokerActor; +import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.message.MessageInstance; import org.apache.qpid.server.message.ServerMessage; import org.apache.qpid.server.model.Broker; @@ -56,16 +62,19 @@ import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.QueueType; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.Statistics; import org.apache.qpid.server.model.UUIDGenerator; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.model.VirtualHostAlias; -import org.apache.qpid.server.configuration.updater.TaskExecutor; import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.plugin.ReplicationNodeFactory; +import org.apache.qpid.server.plugin.VirtualHostFactory; import org.apache.qpid.server.protocol.AMQConnectionModel; import org.apache.qpid.server.queue.AMQQueue; import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.replication.ReplicationGroupListener; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.security.access.Operation; import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; @@ -74,15 +83,15 @@ import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.txn.LocalTransaction; import org.apache.qpid.server.txn.ServerTransaction; import org.apache.qpid.server.util.MapValueConverter; -import org.apache.qpid.server.plugin.VirtualHostFactory; import org.apache.qpid.server.virtualhost.ExchangeExistsException; import org.apache.qpid.server.virtualhost.ReservedExchangeNameException; import org.apache.qpid.server.virtualhost.UnknownExchangeException; +import org.apache.qpid.server.virtualhost.VirtualHostAttributeRecoveryListener; import org.apache.qpid.server.virtualhost.VirtualHostListener; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; import org.apache.qpid.server.virtualhost.plugins.QueueExistsException; -public final class VirtualHostAdapter extends AbstractAdapter implements VirtualHost, VirtualHostListener +public final class VirtualHostAdapter extends AbstractAdapter implements VirtualHost, VirtualHostListener, ReplicationGroupListener, VirtualHostAttributeRecoveryListener { private static final Logger LOGGER = Logger.getLogger(VirtualHostAdapter.class); @@ -93,10 +102,23 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual put(STORE_PATH, String.class); put(STORE_TYPE, String.class); put(CONFIG_PATH, String.class); - put(STATE, State.class); + put(DESIRED_STATE, State.class); + put(REMOTE_REPLICATION_NODE_MONITOR_INTERVAL, Long.class); + put(QUIESCE_ON_MASTER_CHANGE, Boolean.class); }}); - private org.apache.qpid.server.virtualhost.VirtualHost _virtualHost; + public static final List<String> UPDATABLE_ATTRIBUTES = Collections.unmodifiableList(Arrays.asList(DESIRED_STATE)); + + private static final long DEFAULT_REMOTE_REPLICATION_NODE_MONITOR_INTERVAL = 10000L; + + @SuppressWarnings("serial") + static final Map<String, Object> DEFAULTS = new HashMap<String, Object>() + {{ + put(REMOTE_REPLICATION_NODE_MONITOR_INTERVAL, DEFAULT_REMOTE_REPLICATION_NODE_MONITOR_INTERVAL); + put(QUIESCE_ON_MASTER_CHANGE, false); + }}; + + private volatile org.apache.qpid.server.virtualhost.VirtualHost _virtualHost; private final Map<AMQConnectionModel, ConnectionAdapter> _connectionAdapters = new HashMap<AMQConnectionModel, ConnectionAdapter>(); @@ -111,13 +133,22 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual private final List<VirtualHostAlias> _aliases = new ArrayList<VirtualHostAlias>(); private StatisticsGatherer _brokerStatisticsGatherer; + private final List<ReplicationNode> _replicationNodes = new ArrayList<ReplicationNode>(); + + private final TaskExecutor _taskExecutor; + private final AtomicReference<State> _state; + public VirtualHostAdapter(UUID id, Map<String, Object> attributes, Broker broker, StatisticsGatherer brokerStatisticsGatherer, TaskExecutor taskExecutor) { - super(id, null, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES, false), taskExecutor, false); + super(id, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES, false), taskExecutor, false); + _taskExecutor = taskExecutor; _broker = broker; _brokerStatisticsGatherer = brokerStatisticsGatherer; - validateAttributes(); addParent(Broker.class, broker); + _state = new AtomicReference<State>(State.INITIALISING); + validateAttributes(); + _virtualHost = createVirtualHostImpl(); + _statistics = new VirtualHostStatisticsAdapter(_virtualHost); } private void validateAttributes() @@ -142,29 +173,12 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual { validateAttributes(type); } - }/* - else - { - if (type != null) - { - invalidAttributes = true; - } + } - }*/ if (invalidAttributes) { throw new IllegalConfigurationException("Please specify either the 'configPath' attribute or 'type' attributes"); } - - // pre-load the configuration in order to validate - try - { - createVirtualHostConfiguration(name); - } - catch(ConfigurationException e) - { - throw new IllegalConfigurationException("Failed to validate configuration", e); - } } private void validateAttributes(String type) @@ -262,6 +276,15 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual } } + @Override + public Collection<ReplicationNode> getReplicationNodes() + { + synchronized (_replicationNodes) + { + return Collections.unmodifiableList(_replicationNodes); + } + } + public Exchange createExchange(Map<String, Object> attributes) throws AccessControlException, IllegalArgumentException @@ -501,18 +524,14 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual @Override public State getActualState() { - if (_virtualHost == null) + org.apache.qpid.server.virtualhost.VirtualHost virtualHost = _virtualHost; + if (virtualHost == null) { - State state = (State)super.getAttribute(STATE); - if (state == null) - { - return State.INITIALISING; - } - return state; + return _state.get(); } else { - org.apache.qpid.server.virtualhost.State implementationState = _virtualHost.getState(); + org.apache.qpid.server.virtualhost.State implementationState = virtualHost.getState(); switch(implementationState) { case INITIALISING: @@ -520,11 +539,13 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual case ACTIVE: return State.ACTIVE; case PASSIVE: - return State.REPLICA; + return State.UNAVAILABLE; case STOPPED: return State.STOPPED; case ERRORED: return State.ERRORED; + case QUIESCED: + return State.QUIESCED; default: throw new IllegalStateException("Unsupported state:" + implementationState); } @@ -569,6 +590,7 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual return _statistics; } + @SuppressWarnings("unchecked") @Override public <C extends ConfiguredObject> Collection<C> getChildren(Class<C> clazz) { @@ -588,12 +610,17 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual { return (Collection<C>) getAliases(); } + else if (clazz == ReplicationNode.class) + { + return (Collection<C>)getReplicationNodes(); + } else { return Collections.emptySet(); } } + @SuppressWarnings("unchecked") @Override public <C extends ConfiguredObject> C addChild(Class<C> childClass, Map<String, Object> attributes, ConfiguredObject... otherParents) { @@ -621,6 +648,11 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual { throw new UnsupportedOperationException(); } + else if(childClass == ReplicationNode.class) + { + return (C)createReplicationNode(attributes); + } + throw new IllegalArgumentException("Cannot create a child of class " + childClass.getSimpleName()); } @@ -901,6 +933,7 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual private Object getAttributeFromVirtualHostImplementation(String name) { + MessageStore messageStore = _virtualHost.getMessageStore(); if(SUPPORTED_EXCHANGE_TYPES.equals(name)) { List<String> types = new ArrayList<String>(); @@ -934,13 +967,13 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual { return _virtualHost.getConfiguration().getFlowResumeCapacity(); } - else if(STORE_TYPE.equals(name)) + else if(STORE_TYPE.equals(name) && messageStore != null) { - return _virtualHost.getMessageStore().getStoreType(); + return messageStore.getStoreType(); } - else if(STORE_PATH.equals(name)) + else if(STORE_PATH.equals(name) && messageStore != null && messageStore.getStoreLocation() != null) { - return _virtualHost.getMessageStore().getStoreLocation(); + return messageStore.getStoreLocation(); } else if(STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE.equals(name)) { @@ -1046,29 +1079,40 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual @Override protected boolean setState(State currentState, State desiredState) { + State actualState = getActualState(); if (desiredState == State.ACTIVE) { - try - { - activate(); - } - catch(RuntimeException e) + if( _state.compareAndSet(State.INITIALISING, State.ACTIVE) || _state.compareAndSet(State.STOPPED, State.ACTIVE) + || _state.compareAndSet(State.QUIESCED, State.ACTIVE) || _state.compareAndSet(State.ERRORED, State.ACTIVE)) + { + try + { + activate(); + } + catch(Exception e) + { + _state.set(State.ERRORED); + if (_broker.isManagementMode()) + { + LOGGER.warn("Failed to activate virtual host: " + getName(), e); + } + else + { + throw new IllegalStateException("Failed to activate virtual host: " + getName(), e); + } + } + return true; + } + else { - changeAttribute(STATE, State.INITIALISING, State.ERRORED); - if (_broker.isManagementMode()) - { - LOGGER.warn("Failed to activate virtual host: " + getName(), e); - } - else - { - throw e; - } + throw new IllegalStateException("Cannot activate host with state " + actualState); } - return true; } else if (desiredState == State.STOPPED) { - if (_virtualHost != null) + if (_state.compareAndSet(State.ACTIVE, State.STOPPED) || _state.compareAndSet(State.INITIALISING, State.STOPPED) + || _state.compareAndSet(State.QUIESCED, State.STOPPED) || _state.compareAndSet(State.UNAVAILABLE, State.STOPPED) + || _state.compareAndSet(State.ERRORED, State.STOPPED)) { try { @@ -1076,12 +1120,18 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual } finally { - _broker.getVirtualHostRegistry().unregisterVirtualHost(_virtualHost); + _queueAdapters.clear(); + _exchangeAdapters.clear(); + _aliases.clear(); } + return true; + } + else + { + throw new IllegalStateException("Cannot stop host with state " + actualState); } - return true; } - else if (desiredState == State.DELETED) + else if (desiredState == State.DELETED && actualState != State.DELETED) { String hostName = getName(); @@ -1089,13 +1139,15 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual { throw new IntegrityViolationException("Cannot delete default virtual host '" + hostName + "'"); } - if (_virtualHost != null) + + if (actualState == State.ACTIVE) { - if (_virtualHost.getState() == org.apache.qpid.server.virtualhost.State.ACTIVE) - { - setDesiredState(currentState, State.STOPPED); - } + setDesiredState(actualState, State.STOPPED); + } + close(); + if (_virtualHost != null) + { MessageStore ms = _virtualHost.getMessageStore(); if (ms != null) { @@ -1111,19 +1163,35 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual _virtualHost = null; } - setAttribute(VirtualHost.STATE, getActualState(), State.DELETED); + + _state.set(State.DELETED); + return true; } + else if (desiredState == State.QUIESCED) + { + if( _state.compareAndSet(State.INITIALISING, State.QUIESCED) || _state.compareAndSet(State.STOPPED, State.QUIESCED) + || _state.compareAndSet(State.ACTIVE, State.QUIESCED) || _state.compareAndSet(State.ERRORED, State.QUIESCED)) + { + _virtualHost.quiesce(); + return true; + } + else + { + throw new IllegalStateException("Cannot quiesce host with state " + actualState); + } + } return false; } - private void activate() + private org.apache.qpid.server.virtualhost.VirtualHost createVirtualHostImpl() { + org.apache.qpid.server.virtualhost.VirtualHost virtualHost = null; VirtualHostRegistry virtualHostRegistry = _broker.getVirtualHostRegistry(); String virtualHostName = getName(); try { - VirtualHostConfiguration configuration = createVirtualHostConfiguration(virtualHostName); + VirtualHostConfiguration configuration = createVirtualHostConfiguration(virtualHostName, this); String type = configuration.getType(); final VirtualHostFactory factory = VirtualHostFactory.FACTORIES.get(type); if(factory == null) @@ -1132,11 +1200,19 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual } else { - _virtualHost = factory.createVirtualHost(_broker.getVirtualHostRegistry(), + CurrentActor.set(new BrokerActor(_broker.getRootMessageLogger())); + try + { + virtualHost = factory.createVirtualHost(_broker.getVirtualHostRegistry(), _brokerStatisticsGatherer, _broker.getSecurityManager(), configuration, this); + } + finally + { + CurrentActor.remove(); + } } } catch (Exception e) @@ -1144,10 +1220,16 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual throw new RuntimeException("Failed to create virtual host " + virtualHostName, e); } - virtualHostRegistry.registerVirtualHost(_virtualHost); + virtualHostRegistry.registerVirtualHost(virtualHost); + virtualHost.addVirtualHostListener(this); + virtualHost.setReplicationGroupListener(this); + virtualHost.setVirtualHostAttributeRecoveryListener(this); + return virtualHost; + } - _statistics = new VirtualHostStatisticsAdapter(_virtualHost); - _virtualHost.addVirtualHostListener(this); + private void activate() throws Exception + { + _virtualHost.activate(); populateQueues(); populateExchanges(); @@ -1163,12 +1245,13 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual } } - private VirtualHostConfiguration createVirtualHostConfiguration(String virtualHostName) throws ConfigurationException + private VirtualHostConfiguration createVirtualHostConfiguration(String virtualHostName, ReplicationGroupListener listener) throws ConfigurationException { VirtualHostConfiguration configuration; String configurationFile = (String)getAttribute(CONFIG_PATH); if (configurationFile == null) { + LOGGER.debug("Creating virtual host configuration from the attributes"); final MyConfiguration basicConfiguration = new MyConfiguration(); PropertiesConfiguration config = new PropertiesConfiguration(); final String type = (String) getAttribute(TYPE); @@ -1190,6 +1273,10 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual } else { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Creating virtual host configuration from configuration file " + configurationFile); + } if (!new File(configurationFile).exists()) { throw new IllegalConfigurationException("Configuration file '" + configurationFile + "' does not exist"); @@ -1205,7 +1292,15 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual changeAttribute(entry.getKey(), getAttribute(entry.getKey()), entry.getValue()); } } - + ReplicationNode replicationNode = factory.createReplicationNode(configuration.getConfig(), this); + if (listener != null && replicationNode != null) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Replication node " + replicationNode); + } + listener.onReplicationNodeRecovered(replicationNode); + } } return configuration; } @@ -1223,9 +1318,67 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual } @Override + public TaskExecutor getTaskExecutor() + { + return _taskExecutor; + } + + @Override protected void changeAttributes(Map<String, Object> attributes) { - throw new UnsupportedOperationException("Changing attributes on virtualhosts is not supported."); + checkWhetherAttributeChangeIsSupported(attributes); + Map<String, Object> convertedAttributes = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + super.changeAttributes(convertedAttributes); + } + + private void checkWhetherAttributeChangeIsSupported(Map<String, Object> attributes) + { + for (String attributeName : attributes.keySet()) + { + // the name is appended into attributes map in REST layer + if (attributeName.equals(NAME) && getName().equals(attributes.get(NAME))) + { + continue; + } + + if (!UPDATABLE_ATTRIBUTES.contains(attributeName)) + { + throw new IllegalConfigurationException("Cannot change value of attribute " + attributeName); + } + } + } + + @Override + protected boolean changeAttribute(final String name, final Object expected, final Object desired) + { + if (DESIRED_STATE.equals(name)) + { + return changeDesiredStateAttribute(expected, desired); + } + return super.changeAttribute(name, expected, desired); + } + + private boolean changeDesiredStateAttribute(final Object expected, final Object desired) + { + State expectedState = (State)expected; + State desiredState = (State)desired; + + if (desiredState == State.UNAVAILABLE || desired == State.ERRORED) + { + throw new IllegalConfigurationException("Changing state to " + State.UNAVAILABLE + " or " + State.ERRORED + " is not allowed"); + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug(String.format("Change state of virtual host '%s' from '%s' to '%s'", getName(), expectedState, desiredState)); + } + + if (super.changeAttribute(DESIRED_STATE, expected, desired)) + { + setDesiredState(expectedState, desiredState); + return true; + } + return false ; } @Override @@ -1257,4 +1410,92 @@ public final class VirtualHostAdapter extends AbstractAdapter implements Virtual throw new AccessControlException("Setting of virtual host attributes is denied"); } } + + @Override + public void onReplicationNodeRecovered(ReplicationNode node) + { + //TODO: should we be adding ConfigurationChangeListener to node? + _replicationNodes.add(node); + } + + @Override + public void onReplicationNodeAddedToGroup(ReplicationNode node) + { + _replicationNodes.add(node); + } + + @Override + public void onReplicationNodeRemovedFromGroup(ReplicationNode node) + { + _replicationNodes.remove(node); + } + + public void recoverChild(ConfiguredObject configuredObject) + { + if (configuredObject instanceof ReplicationNode) + { + ReplicationNode node = (ReplicationNode)configuredObject; + if (!_replicationNodes.isEmpty()) + { + throw new IllegalStateException("Replication node cannot be recovered because virtual host already contains replication node"); + } + onReplicationNodeRecovered(node); + } + else + { + throw new IllegalArgumentException("Cannot recover child of type :" + configuredObject.getClass().getName()); + } + } + + private ReplicationNode createReplicationNode(Map<String, Object> attributes) + { + ReplicationNode node = null; + + String type = getType(); + ReplicationNodeFactory factory = ReplicationNodeFactory.FACTORIES.get(type); + if (factory == null) + { + throw new IllegalConfigurationException("Cannot find replication node factory for type " + type); + } + + String groupName = MapValueConverter.getStringAttribute(ReplicationNode.GROUP_NAME, attributes); + String nodeName = MapValueConverter.getStringAttribute(ReplicationNode.NAME, attributes); + + synchronized (_replicationNodes) + { + if (!_replicationNodes.isEmpty()) + { + throw new IllegalStateException("Replication node cannot be created because virtual host already contains replication node"); + } + node = factory.createInstance(UUIDGenerator.generateReplicationNodeId(groupName, nodeName), attributes, this); + node.attainDesiredState(); + + _replicationNodes.add(node); + } + //TODO: make VirtualHost a ConfigurationChangeListener and add it to node to listen for delete events + return node; + } + + @Override + public void close() + { + if (_virtualHost != null) + { + try + { + _virtualHost.close(); + } + finally + { + _broker.getVirtualHostRegistry().unregisterVirtualHost(_virtualHost); + } + } + } + + @Override + public void attributesRecovered(Map<String, Object> attributes) + { + changeAttributes(attributes); + } + } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PluggableFactoryLoader.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PluggableFactoryLoader.java index 7a8b7c0c65..d1389be713 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PluggableFactoryLoader.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PluggableFactoryLoader.java @@ -31,11 +31,17 @@ public class PluggableFactoryLoader<T extends Pluggable> private final Map<String, T> _factoriesMap; private final Set<String> _types; + public PluggableFactoryLoader(Class<T> factoryClass) { + this(factoryClass, true); + } + + public PluggableFactoryLoader(Class<T> factoryClass, boolean atLeastOnce) + { Map<String, T> fm = new HashMap<String, T>(); QpidServiceLoader<T> qpidServiceLoader = new QpidServiceLoader<T>(); - Iterable<T> factories = qpidServiceLoader.atLeastOneInstanceOf(factoryClass); + Iterable<T> factories = atLeastOnce? qpidServiceLoader.atLeastOneInstanceOf(factoryClass) : qpidServiceLoader.instancesOf(factoryClass); for (T factory : factories) { String descriptiveType = factory.getType(); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ReplicationNodeFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ReplicationNodeFactory.java new file mode 100644 index 0000000000..e2a0a1ff64 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ReplicationNodeFactory.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.plugin; + +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.model.ReplicationNode; +import org.apache.qpid.server.model.VirtualHost; + +public interface ReplicationNodeFactory extends Pluggable +{ + + PluggableFactoryLoader<ReplicationNodeFactory> FACTORIES = new PluggableFactoryLoader<ReplicationNodeFactory>(ReplicationNodeFactory.class, false); + + ReplicationNode createInstance(UUID id, Map<String, Object> attributes, VirtualHost virtualHost); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/VirtualHostFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/VirtualHostFactory.java index 9549b70c83..fd1ca7cca1 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/VirtualHostFactory.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/VirtualHostFactory.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import org.apache.commons.configuration.Configuration; import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.server.model.adapter.VirtualHostAdapter; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.stats.StatisticsGatherer; @@ -49,6 +50,8 @@ public interface VirtualHostFactory extends Pluggable Map<String,Object> convertVirtualHostConfiguration(Configuration configuration); + ReplicationNode createReplicationNode(Configuration configuration, org.apache.qpid.server.model.VirtualHost virtualHost); + static final class TYPES { private TYPES() diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java index cc1b22d8e1..e861937ed4 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java @@ -23,6 +23,7 @@ package org.apache.qpid.server.registry; import java.util.Collection; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.Callable; import org.apache.log4j.Logger; import org.apache.qpid.common.Closeable; @@ -106,7 +107,7 @@ public class ApplicationRegistry implements IApplicationRegistry BrokerActor actor = new BrokerActor(startupMessageLogger); CurrentActor.set(actor); - CurrentActor.setDefault(actor); + CurrentActor.setDefault(new BrokerActor(_rootMessageLogger)); GenericActor.setDefaultMessageLogger(_rootMessageLogger); try { @@ -125,7 +126,7 @@ public class ApplicationRegistry implements IApplicationRegistry initialiseStatisticsReporting(); // starting the broker - _broker.setDesiredState(State.INITIALISING, State.ACTIVE); + _broker.attainDesiredState(); CurrentActor.get().message(BrokerMessages.READY()); } @@ -133,8 +134,6 @@ public class ApplicationRegistry implements IApplicationRegistry { CurrentActor.remove(); } - - CurrentActor.setDefault(new BrokerActor(_rootMessageLogger)); } private void initialiseStatisticsReporting() @@ -253,7 +252,14 @@ public class ApplicationRegistry implements IApplicationRegistry if (_broker != null) { - _broker.setDesiredState(_broker.getActualState(), State.STOPPED); + _taskExecutor.submitAndWait(new Callable<Void>(){ + + @Override + public Void call() throws Exception + { + _broker.close(); + return null; + }}); } //Shutdown virtualhosts diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/replication/ReplicationGroupListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/replication/ReplicationGroupListener.java new file mode 100644 index 0000000000..d2a6095e5f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/replication/ReplicationGroupListener.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.replication; + +import org.apache.qpid.server.model.ReplicationNode; + +public interface ReplicationGroupListener +{ + /** + * Fired when a remote replication node is added to a group. This event happens + * exactly once just after a new replication node is created. + */ + void onReplicationNodeAddedToGroup(ReplicationNode node); + + /** + * Fired exactly once for each existing remote node. Used to inform the application + * on any existing nodes as it starts up for the first time. + */ + void onReplicationNodeRecovered(ReplicationNode node); + + /** + * Fired when a remote replication node is (permanently) removed from group. This event + * happens exactly once just after the existing replication node is deleted. + */ + void onReplicationNodeRemovedFromGroup(ReplicationNode node); + + /** + * Fired when a remote replication node (that is already a member of the group) joins + * the group. This will typically occur when another replication node is started perhaps + * because the broker has been started. + */ + //void onReplicationNodeUp(); + + /** + * Fired when a remote replication node (that is already a member of the group) leaves + * the group. This will typically occur when another replication node is stopped perhaps + * because its broker has been stopped. + */ + //void onReplicationNodeDown(); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractDurableConfiguredObjectRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractDurableConfiguredObjectRecoverer.java index dbe8bf22a0..3d91420fc5 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractDurableConfiguredObjectRecoverer.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractDurableConfiguredObjectRecoverer.java @@ -20,11 +20,16 @@ */ package org.apache.qpid.server.store; +import java.util.Arrays; import java.util.Map; import java.util.UUID; +import org.apache.log4j.Logger; + public abstract class AbstractDurableConfiguredObjectRecoverer<T> implements DurableConfiguredObjectRecoverer { + private static final Logger LOGGER = Logger.getLogger(AbstractDurableConfiguredObjectRecoverer.class); + @Override public void load(final DurableConfigurationRecoverer durableConfigurationRecoverer, final UUID id, @@ -67,7 +72,6 @@ public abstract class AbstractDurableConfiguredObjectRecoverer<T> implements Dur { durableConfigurationRecoverer.addUnresolvedObject(getType(), id, obj); } - } public abstract UnresolvedObject<T> createUnresolvedObject(final UUID id, diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractJDBCMessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractJDBCMessageStore.java index d80fa656e7..eb3bc9c9e0 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractJDBCMessageStore.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractJDBCMessageStore.java @@ -177,7 +177,7 @@ abstract public class AbstractJDBCMessageStore implements MessageStore, DurableC } @Override - public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler configRecoveryHandler) throws Exception + public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler configRecoveryHandler) { _stateManager.attainState(State.INITIALISING); _configRecoveryHandler = configRecoveryHandler; @@ -187,7 +187,7 @@ abstract public class AbstractJDBCMessageStore implements MessageStore, DurableC @Override public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler recoveryHandler, - TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception + TransactionLogRecoveryHandler tlogRecoveryHandler) throws AMQStoreException { if(_stateManager.isInState(State.INITIAL)) { @@ -197,8 +197,14 @@ abstract public class AbstractJDBCMessageStore implements MessageStore, DurableC _virtualHost = virtualHost; _tlogRecoveryHandler = tlogRecoveryHandler; _messageRecoveryHandler = recoveryHandler; - - completeInitialisation(); + try + { + completeInitialisation(); + } + catch(Exception e) + { + throw new AMQStoreException("Cannot initialize store", e); + } } private void completeInitialisation() throws ClassNotFoundException, SQLException, AMQStoreException @@ -209,8 +215,10 @@ abstract public class AbstractJDBCMessageStore implements MessageStore, DurableC } @Override - public void activate() throws Exception + public void activate() throws AMQStoreException { + try + { if(_stateManager.isInState(State.INITIALISING)) { completeInitialisation(); @@ -234,6 +242,11 @@ abstract public class AbstractJDBCMessageStore implements MessageStore, DurableC } _stateManager.attainState(State.ACTIVE); + } + catch(Exception e) + { + throw new AMQStoreException("Cannot activate store", e); + } } private void commonConfiguration() @@ -668,7 +681,7 @@ abstract public class AbstractJDBCMessageStore implements MessageStore, DurableC } @Override - public void close() throws Exception + public void close() throws AMQStoreException { if (_closed.compareAndSet(false, true)) { @@ -681,7 +694,7 @@ abstract public class AbstractJDBCMessageStore implements MessageStore, DurableC } - protected abstract void doClose() throws Exception; + protected abstract void doClose() throws AMQStoreException; @Override public StoredMessage addMessage(StorableMessageMetaData metaData) diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractMemoryMessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractMemoryMessageStore.java index 1a7cd72cb6..4813edd30b 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractMemoryMessageStore.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractMemoryMessageStore.java @@ -80,14 +80,14 @@ abstract public class AbstractMemoryMessageStore extends NullMessageStore } @Override - public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws Exception + public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) { _stateManager.attainState(State.INITIALISING); } @Override public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler recoveryHandler, - TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception + TransactionLogRecoveryHandler tlogRecoveryHandler) { if(_stateManager.isInState(State.INITIAL)) { @@ -97,7 +97,7 @@ abstract public class AbstractMemoryMessageStore extends NullMessageStore } @Override - public void activate() throws Exception + public void activate() { if(_stateManager.isInState(State.INITIALISING)) @@ -110,7 +110,7 @@ abstract public class AbstractMemoryMessageStore extends NullMessageStore } @Override - public StoredMessage addMessage(StorableMessageMetaData metaData) + public StoredMessage<? extends StorableMessageMetaData> addMessage(StorableMessageMetaData metaData) { final long id = _messageId.getAndIncrement(); StoredMemoryMessage message = new StoredMemoryMessage(id, metaData); @@ -131,7 +131,7 @@ abstract public class AbstractMemoryMessageStore extends NullMessageStore } @Override - public void close() throws Exception + public void close() { if (_closed.compareAndSet(false, true)) { diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java index c6ebe90802..d99862b104 100755 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java @@ -27,7 +27,7 @@ public interface ConfigurationRecoveryHandler { void beginConfigurationRecovery(DurableConfigurationStore store, int configVersion); - void configuredObject(UUID id, String type, Map<String, Object> attributes); + void configuredObject(UUID id, String type, Map<String, Object> attributes) throws RecoveryAbortException; /** * diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationRecoverer.java index 1caaedf6dc..44417bea97 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationRecoverer.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationRecoverer.java @@ -68,6 +68,7 @@ public class DurableConfigurationRecoverer implements ConfigurationRecoveryHandl @Override public void beginConfigurationRecovery(final DurableConfigurationStore store, final int configVersion) { + reset(); _logSubject = new MessageStoreLogSubject(_name, store.getClass().getSimpleName()); _store = store; @@ -103,10 +104,18 @@ public class DurableConfigurationRecoverer implements ConfigurationRecoveryHandl checkUnresolvedDependencies(); applyUpgrade(); + reset(); CurrentActor.get().message(_logSubject, ConfigStoreMessages.RECOVERY_COMPLETE()); return CURRENT_CONFIG_VERSION; } + private void reset() + { + _resolvedObjects.clear(); + _unresolvedObjects.clear(); + _dependencyListeners.clear(); + } + private void applyUpgrade() { diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java index 9c9ce7df59..538a5daa64 100755 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java @@ -28,7 +28,6 @@ import org.apache.qpid.server.model.VirtualHost; public interface DurableConfigurationStore { - public static interface Source { DurableConfigurationStore getDurableConfigurationStore(); @@ -46,7 +45,7 @@ public interface DurableConfigurationStore * @param recoveryHandler Handler to be called as the store recovers on start up * @throws Exception If any error occurs that means the store is unable to configure itself. */ - void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws Exception; + void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws AMQStoreException; /** @@ -90,5 +89,5 @@ public interface DurableConfigurationStore public void update(boolean createIfNecessary, ConfiguredObjectRecord... records) throws AMQStoreException; - void close() throws Exception; + void close() throws AMQStoreException; } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfiguredObjectRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfiguredObjectRecoverer.java index e065728bd3..ea1c3794d9 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfiguredObjectRecoverer.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfiguredObjectRecoverer.java @@ -27,7 +27,7 @@ public interface DurableConfiguredObjectRecoverer { public void load(final DurableConfigurationRecoverer durableConfigurationRecoverer, final UUID id, - final Map<String, Object> attributes); + final Map<String, Object> attributes) throws RecoveryAbortException; public String getType(); } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStore.java index 9b20c9a780..c1d9838833 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStore.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStore.java @@ -65,8 +65,7 @@ public class JsonFileConfigStore implements DurableConfigurationStore } @Override - public void configureConfigStore(final VirtualHost virtualHost, final ConfigurationRecoveryHandler recoveryHandler) - throws Exception + public void configureConfigStore(final VirtualHost virtualHost, final ConfigurationRecoveryHandler recoveryHandler) throws AMQStoreException { _name = virtualHost.getName(); @@ -75,38 +74,44 @@ public class JsonFileConfigStore implements DurableConfigurationStore { throw new AMQStoreException("Cannot determine path for configuration storage"); } - _directoryName = (String) storePathAttr; - _configFileName = _name + ".json"; - _backupFileName = _name + ".bak"; - checkDirectoryIsWritable(_directoryName); - getFileLock(); - - if(!fileExists(_configFileName)) + try { - if(!fileExists(_backupFileName)) + _directoryName = (String) storePathAttr; + _configFileName = _name + ".json"; + _backupFileName = _name + ".bak"; + checkDirectoryIsWritable(_directoryName); + getFileLock(); + + if(!fileExists(_configFileName)) { - File newFile = new File(_directoryName, _configFileName); - _objectMapper.writeValue(newFile, Collections.emptyMap()); + if(!fileExists(_backupFileName)) + { + File newFile = new File(_directoryName, _configFileName); + _objectMapper.writeValue(newFile, Collections.emptyMap()); + } + else + { + renameFile(_backupFileName, _configFileName); + } } - else + + load(); + recoveryHandler.beginConfigurationRecovery(this,_configVersion); + List<ConfiguredObjectRecord> records = new ArrayList<ConfiguredObjectRecord>(_objectsById.values()); + for(ConfiguredObjectRecord record : records) { - renameFile(_backupFileName, _configFileName); + recoveryHandler.configuredObject(record.getId(), record.getType(), record.getAttributes()); + } + int oldConfigVersion = _configVersion; + _configVersion = recoveryHandler.completeConfigurationRecovery(); + if(oldConfigVersion != _configVersion) + { + save(); } } - - - load(); - recoveryHandler.beginConfigurationRecovery(this,_configVersion); - List<ConfiguredObjectRecord> records = new ArrayList<ConfiguredObjectRecord>(_objectsById.values()); - for(ConfiguredObjectRecord record : records) - { - recoveryHandler.configuredObject(record.getId(), record.getType(), record.getAttributes()); - } - int oldConfigVersion = _configVersion; - _configVersion = recoveryHandler.completeConfigurationRecovery(); - if(oldConfigVersion != _configVersion) + catch(IOException e) { - save(); + throw new AMQStoreException("Cannot configure store", e); } } @@ -478,12 +483,16 @@ public class JsonFileConfigStore implements DurableConfigurationStore save(); } - public void close() throws Exception + public void close() throws AMQStoreException { try { releaseFileLock(); } + catch(Exception e) + { + throw new AMQStoreException("Cannot release file lock", e); + } finally { _fileLock = null; diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStore.java index 996d71d51d..55a0ca009a 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStore.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStore.java @@ -20,6 +20,7 @@ */ package org.apache.qpid.server.store; +import org.apache.qpid.AMQStoreException; import org.apache.qpid.server.model.VirtualHost; /** @@ -41,11 +42,11 @@ public interface MessageStore * @throws Exception If any error occurs that means the store is unable to configure itself. */ void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler messageRecoveryHandler, - TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception; + TransactionLogRecoveryHandler tlogRecoveryHandler) throws AMQStoreException; - void activate() throws Exception; + void activate() throws AMQStoreException; - public <T extends StorableMessageMetaData> StoredMessage<T> addMessage(T metaData); + public <T extends StorableMessageMetaData> StoredMessage<T> addMessage(T metaData) throws AMQStoreException; /** @@ -55,14 +56,14 @@ public interface MessageStore */ boolean isPersistent(); - Transaction newTransaction(); + Transaction newTransaction() throws AMQStoreException; /** * Called to close and cleanup any resources used by the message store. * * @throws Exception If the close fails. */ - void close() throws Exception; + void close() throws AMQStoreException; void addEventListener(EventListener eventListener, Event... events); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullMessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullMessageStore.java index 57dbfabaa4..99b32d347f 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullMessageStore.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullMessageStore.java @@ -27,7 +27,7 @@ import org.apache.qpid.server.model.VirtualHost; public abstract class NullMessageStore implements MessageStore, DurableConfigurationStore { @Override - public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws Exception + public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) { } @@ -42,7 +42,7 @@ public abstract class NullMessageStore implements MessageStore, DurableConfigura } @Override - public void update(boolean createIfNecessary, ConfiguredObjectRecord... records) throws AMQStoreException + public void update(boolean createIfNecessary, ConfiguredObjectRecord... records) { } @@ -65,12 +65,12 @@ public abstract class NullMessageStore implements MessageStore, DurableConfigura @Override public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler recoveryHandler, - TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception + TransactionLogRecoveryHandler tlogRecoveryHandler) { } @Override - public void close() throws Exception + public void close() { } @@ -93,7 +93,7 @@ public abstract class NullMessageStore implements MessageStore, DurableConfigura } @Override - public void activate() throws Exception + public void activate() { } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/RecoveryAbortException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/RecoveryAbortException.java new file mode 100644 index 0000000000..fa6f792493 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/RecoveryAbortException.java @@ -0,0 +1,37 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.store; + +@SuppressWarnings("serial") +public class RecoveryAbortException extends RuntimeException +{ + + public RecoveryAbortException(String message) + { + super(message); + } + + public RecoveryAbortException(String message, Throwable cause) + { + super(message, cause); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StateManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StateManager.java index e4efc26477..9cf28095d2 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StateManager.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StateManager.java @@ -24,10 +24,12 @@ package org.apache.qpid.server.store; import java.util.EnumMap; import java.util.Map; -import org.apache.qpid.server.store.StateManager.Transition; +import org.apache.log4j.Logger; public class StateManager { + private static final Logger LOGGER = Logger.getLogger(StateManager.class); + private State _state = State.INITIAL; private EventListener _eventListener; @@ -79,6 +81,7 @@ public class StateManager public static final Transition ACTIVATE_COMPLETE = new Transition(State.ACTIVATING, State.ACTIVE, Event.AFTER_ACTIVATE); public static final Transition CLOSE_INITIALISED = new Transition(State.INITIALISED, State.CLOSING, Event.BEFORE_CLOSE);; + public static final Transition CLOSE_ACTIVATING = new Transition(State.ACTIVATING, State.CLOSING, Event.BEFORE_CLOSE); public static final Transition CLOSE_ACTIVE = new Transition(State.ACTIVE, State.CLOSING, Event.BEFORE_CLOSE); public static final Transition CLOSE_QUIESCED = new Transition(State.QUIESCED, State.CLOSING, Event.BEFORE_CLOSE); public static final Transition CLOSE_COMPLETE = new Transition(State.CLOSING, State.CLOSED, Event.AFTER_CLOSE); @@ -116,6 +119,11 @@ public class StateManager public synchronized void attainState(State desired) { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Attaining state from " + _state + " to " + desired); + } + Transition transition = null; final Map<State, Transition> stateTransitionMap = _validTransitions.get(_state); if(stateTransitionMap != null) diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedObject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedObject.java index 7ebebadae7..ea49f0787c 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedObject.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedObject.java @@ -24,5 +24,5 @@ public interface UnresolvedObject<T> { public UnresolvedDependency[] getUnresolvedDependencies(); - T resolve(); + T resolve() throws RecoveryAbortException; } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/DaemonThreadFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/DaemonThreadFactory.java new file mode 100644 index 0000000000..4f1f830fd0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/DaemonThreadFactory.java @@ -0,0 +1,40 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.util; + +import java.util.concurrent.ThreadFactory; + +public final class DaemonThreadFactory implements ThreadFactory +{ + private String _threadName; + public DaemonThreadFactory(String threadName) + { + _threadName = threadName; + } + + @Override + public Thread newThread(Runnable r) + { + Thread thread = new Thread(r, _threadName); + thread.setDaemon(true); + return thread; + } +}
\ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapValueConverter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapValueConverter.java index 37e0177b00..c3b339ca6e 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapValueConverter.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapValueConverter.java @@ -313,21 +313,7 @@ public class MapValueConverter else if (typeObject instanceof ParameterizedType) { ParameterizedType parameterizedType= (ParameterizedType)typeObject; - Type type = parameterizedType.getRawType(); - if (type == Set.class) - { - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - if (actualTypeArguments.length != 1) - { - throw new IllegalArgumentException("Set type argument is not specified"); - } - Class<?> classObject = (Class<?>)actualTypeArguments[0]; - value = toSet(rawValue, classObject, attributeName); - } - else - { - throw new IllegalArgumentException("Conversion into " + parameterizedType + " is not yet supported"); - } + value = convertParameterizedType(rawValue, parameterizedType, attributeName); } else { @@ -344,6 +330,62 @@ public class MapValueConverter return attributes; } + private static Object convertParameterizedType(Object rawValue, ParameterizedType parameterizedType, String attributeName) + { + Type type = parameterizedType.getRawType(); + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + Object convertedValue; + if (type == Set.class) + { + if (actualTypeArguments.length != 1) + { + throw new IllegalArgumentException("Unexpected number of Set type arguments " + actualTypeArguments.length); + } + Class<?> classObject = (Class<?>)actualTypeArguments[0]; + convertedValue = toSet(rawValue, classObject, attributeName); + } + else if (type == Map.class) + { + if (actualTypeArguments.length != 2) + { + throw new IllegalArgumentException("Unexpected number of Map type arguments " + actualTypeArguments.length); + } + Class<?> keyClassObject = (Class<?>)actualTypeArguments[0]; + Class<?> valueClassObject = (Class<?>)actualTypeArguments[1]; + convertedValue = toMap(rawValue, keyClassObject, valueClassObject, attributeName); + } + else + { + throw new IllegalArgumentException("Conversion into " + parameterizedType + " is not yet supported"); + } + return convertedValue; + } + + private static <K,V> Map<K, V> toMap(Object rawValue, Class<K> keyClassObject, Class<V> valueClassObject, String attributeName) + { + if (rawValue == null) + { + return null; + } + if (rawValue instanceof Map) + { + Map<K, V> convertedMap = new HashMap<K, V>(); + Map<?, ?> rawMap = (Map<?,?>)rawValue; + + for (Map.Entry<?, ?> entry : rawMap.entrySet()) + { + K convertedKey = convert(entry.getKey(), keyClassObject, attributeName + " (map key)"); + V convertedValue = convert(entry.getValue(), valueClassObject, attributeName + " (map value)"); + convertedMap.put(convertedKey, convertedValue); + } + return convertedMap; + } + else + { + throw new IllegalArgumentException("rawValue is not of unexpected type Map, was : " + rawValue.getClass()); + } + } + public static <T> Set<T> toSet(Object rawValue, Class<T> setItemClass, String attributeName) { if (rawValue == null) @@ -353,7 +395,7 @@ public class MapValueConverter HashSet<T> set = new HashSet<T>(); if (rawValue instanceof Iterable) { - Iterable<?> iterable = (Iterable<?>)rawValue; + Iterable<?> iterable = (Iterable<?>)rawValue; for (Object object : iterable) { T converted = convert(object, setItemClass, attributeName); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java index 3d42b07117..7d2429f907 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java @@ -30,11 +30,14 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.configuration.ConfigurationException; import org.apache.log4j.Logger; import org.apache.qpid.AMQException; import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.AMQStoreException; import org.apache.qpid.server.configuration.ExchangeConfiguration; import org.apache.qpid.server.configuration.QueueConfiguration; import org.apache.qpid.server.configuration.VirtualHostConfiguration; @@ -47,6 +50,8 @@ import org.apache.qpid.server.exchange.ExchangeFactory; import org.apache.qpid.server.exchange.ExchangeRegistry; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.messages.VirtualHostMessages; +import org.apache.qpid.server.model.ConfigurationChangeListener; +import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.message.MessageDestination; import org.apache.qpid.server.message.MessageNode; import org.apache.qpid.server.message.MessageSource; @@ -61,18 +66,21 @@ import org.apache.qpid.server.queue.AMQQueue; import org.apache.qpid.server.queue.AMQQueueFactory; import org.apache.qpid.server.queue.DefaultQueueRegistry; import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.replication.ReplicationGroupListener; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.stats.StatisticsCounter; import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.ConfiguredObjectRecord; import org.apache.qpid.server.store.DurableConfigurationStore; import org.apache.qpid.server.store.DurableConfigurationStoreHelper; import org.apache.qpid.server.store.DurableConfiguredObjectRecoverer; import org.apache.qpid.server.store.Event; import org.apache.qpid.server.store.EventListener; +import org.apache.qpid.server.store.RecoveryAbortException; import org.apache.qpid.server.txn.DtxRegistry; import org.apache.qpid.server.virtualhost.plugins.QueueExistsException; -public abstract class AbstractVirtualHost implements VirtualHost, IConnectionRegistry.RegistryChangeListener, EventListener +public abstract class AbstractVirtualHost implements VirtualHost, IConnectionRegistry.RegistryChangeListener, EventListener, ConfigurationChangeListener { private static final Logger _logger = Logger.getLogger(AbstractVirtualHost.class); @@ -84,8 +92,6 @@ public abstract class AbstractVirtualHost implements VirtualHost, IConnectionReg private final long _createTime = System.currentTimeMillis(); - private final ScheduledThreadPoolExecutor _houseKeepingTasks; - private final VirtualHostRegistry _virtualHostRegistry; private final StatisticsGatherer _brokerStatisticsGatherer; @@ -107,12 +113,22 @@ public abstract class AbstractVirtualHost implements VirtualHost, IConnectionReg private final SystemNodeRegistry _systemNodeRegistry = new SystemNodeRegistry(); private final org.apache.qpid.server.model.VirtualHost _model; - private volatile State _state = State.INITIALISING; + private final Map<String, LinkRegistry> _linkRegistry = new HashMap<String, LinkRegistry>(); - private StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + private final AtomicReference<VirtualHostAttributeRecoveryListener> _virtualHostAttributeRecoveryListener = new AtomicReference<VirtualHostAttributeRecoveryListener>(); + private final AtomicReference<ReplicationGroupListener> _replicationGroupListener = new AtomicReference<ReplicationGroupListener>(); - private final Map<String, LinkRegistry> _linkRegistry = new HashMap<String, LinkRegistry>(); - private boolean _blocked; + /** + * Flag indicating whether virtual host is still active, for instance, + * it can be in QUIESCED state but the connections, queues, etc could be still open. + * Thus, it all active objects needs to be shutdown on close + */ + private final AtomicBoolean _active = new AtomicBoolean(); + + private volatile StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + private volatile boolean _blocked; + private volatile State _state = State.INITIALISING; + private volatile ScheduledThreadPoolExecutor _houseKeepingTasks; private final Map<String, MessageDestination> _systemNodeDestinations = Collections.synchronizedMap(new HashMap<String,MessageDestination>()); @@ -146,16 +162,11 @@ public abstract class AbstractVirtualHost implements VirtualHost, IConnectionReg _id = UUIDGenerator.generateVhostUUID(_name); - CurrentActor.get().message(VirtualHostMessages.CREATED(_name)); - _securityManager = new SecurityManager(parentSecurityManager, _vhostConfig.getConfig().getString("security.acl"), _name); _connectionRegistry = new ConnectionRegistry(); _connectionRegistry.addRegistryChangeListener(this); - _houseKeepingTasks = new ScheduledThreadPoolExecutor(_vhostConfig.getHouseKeepingThreadCount()); - - _queueRegistry = new DefaultQueueRegistry(this); _queueFactory = new AMQQueueFactory(this, _queueRegistry); @@ -168,10 +179,43 @@ public abstract class AbstractVirtualHost implements VirtualHost, IConnectionReg initialiseStatistics(); - initialiseStorage(hostConfig, virtualHost); + CurrentActor.get().message(VirtualHostMessages.CREATED(_name)); + } + + @Override + public void activate() throws Exception + { + State currentState = getState(); + if (_active.compareAndSet(false, true)) + { + _houseKeepingTasks = new ScheduledThreadPoolExecutor(_vhostConfig.getHouseKeepingThreadCount()); + + try + { + initialiseStorage(_vhostConfig, _model); + + getMessageStore().addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_OVERFULL); + getMessageStore().addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); + } + catch(RecoveryAbortException e) + { + _logger.warn("Activation is aborted due to : " + e.getMessage()); + return; + } + } + else + { + if ( currentState == State.QUIESCED) + { + setState(State.ACTIVE); + } + } + } - getMessageStore().addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_OVERFULL); - getMessageStore().addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); + @Override + public void quiesce() + { + setState(State.QUIESCED); } private void registerSystemNodes() @@ -223,6 +267,11 @@ public abstract class AbstractVirtualHost implements VirtualHost, IConnectionReg protected void shutdownHouseKeeping() { + if (_houseKeepingTasks == null) + { + return; + } + _houseKeepingTasks.shutdown(); try @@ -676,23 +725,35 @@ public abstract class AbstractVirtualHost implements VirtualHost, IConnectionReg return _securityManager; } - public void close() + + protected void passivate(String reason) { + _model.removeChangeListener(this); + removeHouseKeepingTasks(); + //Stop Connections - _connectionRegistry.close(); + _connectionRegistry.close(reason); _queueRegistry.stopAllAndUnregisterMBeans(); _dtxRegistry.close(); - closeStorage(); - shutdownHouseKeeping(); // clear exchange objects _exchangeRegistry.clearAndUnregisterMbeans(); + } - _state = State.STOPPED; + public void close() + { + if (_active.compareAndSet(true, false)) + { + passivate(IConnectionRegistry.BROKER_SHUTDOWN_REPLY_TEXT); + closeStorage(); + shutdownHouseKeeping(); + } + _state = State.STOPPED; CurrentActor.get().message(VirtualHostMessages.CLOSED()); } + protected void closeStorage() { //Close MessageStore @@ -884,6 +945,7 @@ public abstract class AbstractVirtualHost implements VirtualHost, IConnectionReg try { initialiseHouseKeeping(_vhostConfig.getHousekeepingCheckPeriod()); + _model.addChangeListener(this); finalState = State.ACTIVE; } finally @@ -904,6 +966,7 @@ public abstract class AbstractVirtualHost implements VirtualHost, IConnectionReg protected Map<String, DurableConfiguredObjectRecoverer> getDurableConfigurationRecoverers() { DurableConfiguredObjectRecoverer[] recoverers = { + new VirtualHostRecoverer(this, getVirtualHostAttributeRecoveryListener()), new QueueRecoverer(this, getExchangeRegistry(), _queueFactory), new ExchangeRecoverer(getExchangeRegistry(), getExchangeFactory()), new BindingRecoverer(this, getExchangeRegistry()) @@ -917,6 +980,70 @@ public abstract class AbstractVirtualHost implements VirtualHost, IConnectionReg return recovererMap; } + @Override + public void setVirtualHostAttributeRecoveryListener(VirtualHostAttributeRecoveryListener listener) + { + if (!_virtualHostAttributeRecoveryListener.compareAndSet(null, listener)) + { + throw new IllegalStateException("Attribute recovery listener is already set on virtual host " + getName()); + } + } + + protected VirtualHostAttributeRecoveryListener getVirtualHostAttributeRecoveryListener() + { + return _virtualHostAttributeRecoveryListener.get(); + } + + @Override + public void setReplicationGroupListener(ReplicationGroupListener listener) + { + if (!_replicationGroupListener.compareAndSet(null, listener)) + { + throw new IllegalStateException("Replication group listener is already set on virtual host " + getName()); + } + } + + protected ReplicationGroupListener getReplicationGroupListener() + { + return _replicationGroupListener.get(); + } + + @Override + public void stateChanged(ConfiguredObject object, org.apache.qpid.server.model.State oldState, org.apache.qpid.server.model.State newState) + { + // no-op + } + + @Override + public void childAdded(ConfiguredObject object, ConfiguredObject child) + { + // no-op + } + + @Override + public void childRemoved(ConfiguredObject object, ConfiguredObject child) + { + // no-op + } + + @Override + public void attributeSet(ConfiguredObject object, String attributeName, Object oldAttributeValue, Object newAttributeValue) + { + DurableConfigurationStore durableConfigurationStore = getDurableConfigurationStore(); + if (durableConfigurationStore != null) + { + try + { + ConfiguredObjectRecord record = new ConfiguredObjectRecord(getId(), org.apache.qpid.server.model.VirtualHost.class.getSimpleName(), Collections.singletonMap(attributeName, newAttributeValue)); + durableConfigurationStore.update(true, record); + } + catch (AMQStoreException e) + { + _logger.error("Can save virtual host attribute " + attributeName + " value " + newAttributeValue, e); + } + } + } + private class VirtualHostHouseKeepingTask extends HouseKeepingTask { public VirtualHostHouseKeepingTask() diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/DefaultUpgraderProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/DefaultUpgraderProvider.java index 12f8c7dae8..c99092fff7 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/DefaultUpgraderProvider.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/DefaultUpgraderProvider.java @@ -63,7 +63,6 @@ public class DefaultUpgraderProvider implements UpgraderProvider currentUpgrader = addUpgrader(currentUpgrader, new Version1Upgrader()); case 2: currentUpgrader = addUpgrader(currentUpgrader, new Version2Upgrader()); - case CURRENT_CONFIG_VERSION: currentUpgrader = addUpgrader(currentUpgrader, new NullUpgrader(recoverer)); break; diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHost.java index b7e51d88d3..8b74915059 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHost.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHost.java @@ -48,6 +48,7 @@ public class StandardVirtualHost extends AbstractVirtualHost private MessageStore initialiseMessageStore(VirtualHostConfiguration hostConfig, VirtualHost virtualHost) throws Exception { + //TODO: we should not be using hostConfig for store creation final Object storeTypeAttr = virtualHost.getAttribute(VirtualHost.STORE_TYPE); String storeType = storeTypeAttr == null ? null : String.valueOf(storeTypeAttr); MessageStore messageStore = null; diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHostFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHostFactory.java index 08f35c08f9..b8641fce4d 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHostFactory.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHostFactory.java @@ -23,6 +23,7 @@ import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.configuration.Configuration; import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.server.model.adapter.VirtualHostAdapter; import org.apache.qpid.server.plugin.MessageStoreFactory; import org.apache.qpid.server.plugin.VirtualHostFactory; @@ -89,6 +90,7 @@ public class StandardVirtualHostFactory implements VirtualHostFactory @Override public Map<String,Object> createVirtualHostConfiguration(VirtualHostAdapter virtualHostAdapter) { + //TODO: DO we really need it? Map<String,Object> convertedMap = new LinkedHashMap<String, Object>(); convertedMap.put("store.type", virtualHostAdapter.getAttribute(org.apache.qpid.server.model.VirtualHost.STORE_TYPE)); convertedMap.put("store.environment-path", virtualHostAdapter.getAttribute(org.apache.qpid.server.model.VirtualHost.STORE_PATH)); @@ -101,6 +103,8 @@ public class StandardVirtualHostFactory implements VirtualHostFactory { Map<String,Object> convertedMap = new LinkedHashMap<String, Object>(); Configuration storeConfiguration = configuration.subset("store"); + + //TODO: If store class is specified, convert class into type convertedMap.put(org.apache.qpid.server.model.VirtualHost.STORE_TYPE, storeConfiguration.getString("type")); convertedMap.put(org.apache.qpid.server.model.VirtualHost.STORE_PATH, storeConfiguration.getString(MessageStoreConstants.ENVIRONMENT_PATH_PROPERTY)); @@ -115,4 +119,10 @@ public class StandardVirtualHostFactory implements VirtualHostFactory return convertedMap; } + + @Override + public ReplicationNode createReplicationNode(Configuration configuration, org.apache.qpid.server.model.VirtualHost virtualHost) + { + return null; + } } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/State.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/State.java index 55e2539dcf..7b5e4ea598 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/State.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/State.java @@ -27,5 +27,6 @@ public enum State PASSIVE, STOPPED, /** Terminal state that signifies the virtual host has experienced an unexpected condition. */ - ERRORED + ERRORED, + QUIESCED } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java index 7034311d84..9edc227813 100755 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java @@ -25,7 +25,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ScheduledFuture; import org.apache.qpid.AMQException; -import org.apache.qpid.AMQSecurityException; import org.apache.qpid.common.Closeable; import org.apache.qpid.server.configuration.VirtualHostConfiguration; import org.apache.qpid.server.connection.IConnectionRegistry; @@ -36,7 +35,7 @@ import org.apache.qpid.server.message.MessageSource; import org.apache.qpid.server.plugin.ExchangeType; import org.apache.qpid.server.protocol.LinkRegistry; import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.replication.ReplicationGroupListener; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.store.DurableConfigurationStore; @@ -102,6 +101,14 @@ public interface VirtualHost extends DurableConfigurationStore.Source, Closeable void close(); + void activate() throws Exception; + + void quiesce(); + + void setVirtualHostAttributeRecoveryListener(VirtualHostAttributeRecoveryListener listener); + + void setReplicationGroupListener(ReplicationGroupListener listener); + UUID getId(); void scheduleHouseKeepingTask(long period, HouseKeepingTask task); diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/HAMessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostAttributeRecoveryListener.java index 59483751ca..67731c0e89 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/HAMessageStore.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostAttributeRecoveryListener.java @@ -1,4 +1,5 @@ /* + * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -17,13 +18,11 @@ * under the License. * */ -package org.apache.qpid.server.store; +package org.apache.qpid.server.virtualhost; + +import java.util.Map; -public interface HAMessageStore extends MessageStore +public interface VirtualHostAttributeRecoveryListener { - /** - * Used to indicate that a store requires to make itself unavailable for read and read/write - * operations. - */ - void passivate(); + public void attributesRecovered(Map<String, Object> attributes); } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostListener.java index 8527435eea..a0258e09a1 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostListener.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostListener.java @@ -20,6 +20,8 @@ */ package org.apache.qpid.server.virtualhost; +import java.util.Map; + import org.apache.qpid.server.exchange.Exchange; import org.apache.qpid.server.protocol.AMQConnectionModel; import org.apache.qpid.server.queue.AMQQueue; @@ -38,4 +40,5 @@ public interface VirtualHostListener public void exchangeRegistered(Exchange exchange); public void exchangeUnregistered(Exchange exchange); + } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRecoverer.java new file mode 100644 index 0000000000..4033bac488 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRecoverer.java @@ -0,0 +1,73 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.server.virtualhost; + +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.store.AbstractDurableConfiguredObjectRecoverer; +import org.apache.qpid.server.store.RecoveryAbortException; +import org.apache.qpid.server.store.UnresolvedDependency; +import org.apache.qpid.server.store.UnresolvedObject; + +public class VirtualHostRecoverer extends AbstractDurableConfiguredObjectRecoverer<VirtualHost> +{ + private final VirtualHost _host; + private final VirtualHostAttributeRecoveryListener _listener; + + public VirtualHostRecoverer(VirtualHost host, VirtualHostAttributeRecoveryListener listener) + { + _host = host; + _listener = listener; + } + + @Override + public String getType() + { + return org.apache.qpid.server.model.VirtualHost.class.getSimpleName(); + } + + @Override + public UnresolvedObject<VirtualHost> createUnresolvedObject(UUID id, String type, final Map<String, Object> attributes) + { + return new UnresolvedObject<VirtualHost>() + { + @Override + public UnresolvedDependency<?>[] getUnresolvedDependencies() + { + return new UnresolvedDependency<?>[0]; + } + + @Override + public VirtualHost resolve() + { + _listener.attributesRecovered(attributes); + Object desiredState = attributes.get(org.apache.qpid.server.model.VirtualHost.DESIRED_STATE); + if (desiredState != null && State.STOPPED.name().equals(desiredState)) + { + throw new RecoveryAbortException("Virtual host state is STOPPED. Aborting the recovery"); + } + return _host; + } + }; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/VirtualHostRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/VirtualHostRecovererTest.java index 422a266efb..4f5bf908c6 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/VirtualHostRecovererTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/VirtualHostRecovererTest.java @@ -35,19 +35,39 @@ import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.util.BrokerTestHelper; import org.apache.qpid.server.virtualhost.StandardVirtualHostFactory; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; import org.apache.qpid.test.utils.TestFileUtils; public class VirtualHostRecovererTest extends TestCase { + + private Broker _broker; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _broker = BrokerTestHelper.createBrokerMock(); + when(_broker.getVirtualHostRegistry()).thenReturn(mock(VirtualHostRegistry.class)); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + BrokerTestHelper.tearDown(); + } + public void testCreate() { StatisticsGatherer statisticsGatherer = mock(StatisticsGatherer.class); SecurityManager securityManager = mock(SecurityManager.class); ConfigurationEntry entry = mock(ConfigurationEntry.class); - Broker parent = mock(Broker.class); - when(parent.getAttribute(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD)).thenReturn(3000l); - when(parent.getSecurityManager()).thenReturn(securityManager); + when(_broker.getAttribute(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD)).thenReturn(3000l); + when(_broker.getSecurityManager()).thenReturn(securityManager); VirtualHostRecoverer recoverer = new VirtualHostRecoverer(statisticsGatherer); Map<String, Object> attributes = new HashMap<String, Object>(); @@ -58,7 +78,7 @@ public class VirtualHostRecovererTest extends TestCase attributes.put(VirtualHost.CONFIG_PATH, file.getAbsolutePath()); when(entry.getAttributes()).thenReturn(attributes); - VirtualHost host = recoverer.create(null, entry, parent); + VirtualHost host = recoverer.create(null, entry, _broker); assertNotNull("Null is returned", host); assertEquals("Unexpected name", getName(), host.getName()); @@ -69,9 +89,9 @@ public class VirtualHostRecovererTest extends TestCase StatisticsGatherer statisticsGatherer = mock(StatisticsGatherer.class); SecurityManager securityManager = mock(SecurityManager.class); ConfigurationEntry entry = mock(ConfigurationEntry.class); - Broker parent = mock(Broker.class); - when(parent.getAttribute(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD)).thenReturn(3000l); - when(parent.getSecurityManager()).thenReturn(securityManager); + + when(_broker.getAttribute(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD)).thenReturn(3000l); + when(_broker.getSecurityManager()).thenReturn(securityManager); VirtualHostRecoverer recoverer = new VirtualHostRecoverer(statisticsGatherer); Map<String, Object> attributes = new HashMap<String, Object>(); @@ -81,7 +101,7 @@ public class VirtualHostRecovererTest extends TestCase attributes.put(VirtualHost.STORE_TYPE, "TESTMEMORY"); when(entry.getAttributes()).thenReturn(attributes); - VirtualHost host = recoverer.create(null, entry, parent); + VirtualHost host = recoverer.create(null, entry, _broker); assertNotNull("Null is returned", host); assertEquals("Unexpected name", getName(), host.getName()); diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java index d419030c1d..8946851bad 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java @@ -22,6 +22,7 @@ package org.apache.qpid.server.configuration.store; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -34,6 +35,7 @@ import org.apache.qpid.server.model.GroupProvider; import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.server.model.Transport; import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.model.VirtualHost; @@ -97,6 +99,8 @@ public abstract class ConfigurationEntryStoreTestCase extends QpidTestCase protected abstract void addConfiguration(UUID id, String type, Map<String, Object> attributes, UUID parentId); + protected abstract ConfigurationEntryStore reOpenStore(); + protected final void addConfiguration(UUID id, String type, Map<String, Object> attributes) { addConfiguration(id, type, attributes, _brokerId); @@ -434,4 +438,53 @@ public abstract class ConfigurationEntryStoreTestCase extends QpidTestCase _store.save(newAuthenticationProviderConfigEntry, preferencesProviderEntry); } + public void testAddHaVirtualHostWithReplicationNode() + { + String nodeName = "nodeName"; + String groupName = "groupName"; + String hostPort = "localhost:9999"; + String helperHostPort = "localhost:8888"; + Map<String, String> parameters = new HashMap<String, String>(); + parameters.put("param1", "value1"); + parameters.put("param2", "value2"); + UUID nodeId = UUID.randomUUID(); + + Map<String, Object> nodeAttributes = new HashMap<String, Object>(); + nodeAttributes.put(ReplicationNode.NAME, nodeName); + nodeAttributes.put(ReplicationNode.GROUP_NAME, groupName); + nodeAttributes.put(ReplicationNode.HOST_PORT, hostPort); + nodeAttributes.put(ReplicationNode.HELPER_HOST_PORT, helperHostPort); + nodeAttributes.put(ReplicationNode.PARAMETERS, parameters); + + ConfigurationEntry nodeEntry = new ConfigurationEntry(nodeId, ReplicationNode.class.getSimpleName(), nodeAttributes, + Collections.<UUID>emptySet(), _store); + Map<String, Object> virtualHostAttributes = new HashMap<String, Object>(); + virtualHostAttributes.put(VirtualHost.NAME, "ha"); + virtualHostAttributes.put(VirtualHost.TYPE, "DUMMY-HA"); + UUID virtualHostId = UUID.randomUUID(); + Set<UUID> childrenIds = Collections.singleton(nodeId); + ConfigurationEntry hostEntry = new ConfigurationEntry(virtualHostId, VirtualHost.class.getSimpleName(), virtualHostAttributes, + childrenIds, _store); + + ConfigurationEntry brokerEntry = _store.getRootEntry(); + Set<UUID> brokerChildren = new HashSet<UUID>(brokerEntry.getChildrenIds()); + brokerChildren.add(virtualHostId); + ConfigurationEntry newRootEntry = new ConfigurationEntry(brokerEntry.getId(), brokerEntry.getType(), brokerEntry.getAttributes(), brokerChildren, _store); + _store.save(hostEntry, nodeEntry, newRootEntry); + + _store = reOpenStore(); + + ConfigurationEntry loadedHostEntry = _store.getEntry(virtualHostId); + + assertEquals("Unexpected type", VirtualHost.class.getSimpleName(), loadedHostEntry.getType()); + assertEquals("Unexpected virtual host id", virtualHostId, loadedHostEntry.getId()); + assertEquals("Unexpected virtual host attributes", virtualHostAttributes, loadedHostEntry.getAttributes()); + assertEquals("Unexpected virtual host children", childrenIds, loadedHostEntry.getChildrenIds()); + + ConfigurationEntry loadedNodeEntry = _store.getEntry(nodeId); + assertEquals("Unexpected type", ReplicationNode.class.getSimpleName(), loadedNodeEntry.getType()); + assertEquals("Unexpected node id", nodeId, loadedNodeEntry.getId()); + assertEquals("Unexpected node attributes", nodeAttributes, loadedNodeEntry.getAttributes()); + assertTrue("Unexpected node children", loadedNodeEntry.getChildrenIds().isEmpty()); + } } diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java index 2c59bfd453..0ca45f5b42 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java @@ -71,6 +71,12 @@ public class JsonConfigurationEntryStoreTest extends ConfigurationEntryStoreTest return store; } + @Override + protected ConfigurationEntryStore reOpenStore() + { + return new JsonConfigurationEntryStore(_storeFile.getAbsolutePath(), null, false, Collections.<String,String>emptyMap()); + } + private File createStoreFile(UUID brokerId, Map<String, Object> brokerAttributes) throws IOException, JsonGenerationException, JsonMappingException { @@ -265,4 +271,5 @@ public class JsonConfigurationEntryStoreTest extends ConfigurationEntryStoreTest assertEquals("Unexpected preferences provider type", FileSystemPreferencesProvider.PROVIDER_TYPE, attributes.get(PreferencesProvider.TYPE)); } + } diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandlerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandlerTest.java deleted file mode 100644 index 34b4fbf1ab..0000000000 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandlerTest.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.qpid.server.configuration.store; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.any; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.UUID; - -import org.apache.qpid.server.BrokerOptions; -import org.apache.qpid.server.configuration.ConfigurationEntry; -import org.apache.qpid.server.configuration.ConfigurationEntryStore; -import org.apache.qpid.server.configuration.IllegalConfigurationException; -import org.apache.qpid.server.model.Broker; -import org.apache.qpid.server.model.Port; -import org.apache.qpid.server.model.Protocol; -import org.apache.qpid.server.model.State; -import org.apache.qpid.server.model.VirtualHost; -import org.apache.qpid.test.utils.QpidTestCase; - -public class ManagementModeStoreHandlerTest extends QpidTestCase -{ - private ManagementModeStoreHandler _handler; - private BrokerOptions _options; - private ConfigurationEntryStore _store; - private ConfigurationEntry _root; - private ConfigurationEntry _portEntry; - private UUID _rootId, _portEntryId; - - protected void setUp() throws Exception - { - super.setUp(); - _rootId = UUID.randomUUID(); - _portEntryId = UUID.randomUUID(); - _store = mock(ConfigurationEntryStore.class); - _root = mock(ConfigurationEntry.class); - _portEntry = mock(ConfigurationEntry.class); - when(_store.getRootEntry()).thenReturn(_root); - when(_root.getId()).thenReturn(_rootId); - when(_portEntry.getId()).thenReturn(_portEntryId); - when(_store.getEntry(_portEntryId)).thenReturn(_portEntry); - when(_store.getEntry(_rootId)).thenReturn(_root); - when(_root.getChildrenIds()).thenReturn(Collections.singleton(_portEntryId)); - when(_portEntry.getType()).thenReturn(Port.class.getSimpleName()); - _options = new BrokerOptions(); - _handler = new ManagementModeStoreHandler(_store, _options); - } - - public void testGetRootEntryWithEmptyOptions() - { - ConfigurationEntry root = _handler.getRootEntry(); - assertEquals("Unexpected root id", _rootId, root.getId()); - assertEquals("Unexpected children", Collections.singleton(_portEntryId), root.getChildrenIds()); - } - - public void testGetRootEntryWithHttpPortOverriden() - { - _options.setManagementModeHttpPortOverride(9090); - _handler = new ManagementModeStoreHandler(_store, _options); - ConfigurationEntry root = _handler.getRootEntry(); - assertEquals("Unexpected root id", _rootId, root.getId()); - Collection<UUID> childrenIds = root.getChildrenIds(); - assertEquals("Unexpected children size", 2, childrenIds.size()); - assertTrue("Store port entry id is not found", childrenIds.contains(_portEntryId)); - } - - public void testGetRootEntryWithRmiPortOverriden() - { - _options.setManagementModeRmiPortOverride(9090); - _handler = new ManagementModeStoreHandler(_store, _options); - ConfigurationEntry root = _handler.getRootEntry(); - assertEquals("Unexpected root id", _rootId, root.getId()); - Collection<UUID> childrenIds = root.getChildrenIds(); - assertEquals("Unexpected children size", 3, childrenIds.size()); - assertTrue("Store port entry id is not found", childrenIds.contains(_portEntryId)); - } - - public void testGetRootEntryWithConnectorPortOverriden() - { - _options.setManagementModeJmxPortOverride(9090); - _handler = new ManagementModeStoreHandler(_store, _options); - ConfigurationEntry root = _handler.getRootEntry(); - assertEquals("Unexpected root id", _rootId, root.getId()); - Collection<UUID> childrenIds = root.getChildrenIds(); - assertEquals("Unexpected children size", 2, childrenIds.size()); - assertTrue("Store port entry id is not found", childrenIds.contains(_portEntryId)); - } - - public void testGetRootEntryWithManagementPortsOverriden() - { - _options.setManagementModeHttpPortOverride(1000); - _options.setManagementModeRmiPortOverride(2000); - _options.setManagementModeJmxPortOverride(3000); - _handler = new ManagementModeStoreHandler(_store, _options); - ConfigurationEntry root = _handler.getRootEntry(); - assertEquals("Unexpected root id", _rootId, root.getId()); - Collection<UUID> childrenIds = root.getChildrenIds(); - assertEquals("Unexpected children size", 4, childrenIds.size()); - assertTrue("Store port entry id is not found", childrenIds.contains(_portEntryId)); - } - - public void testGetEntryByRootId() - { - ConfigurationEntry root = _handler.getEntry(_rootId); - assertEquals("Unexpected root id", _rootId, root.getId()); - assertEquals("Unexpected children", Collections.singleton(_portEntryId), root.getChildrenIds()); - } - - public void testGetEntryByPortId() - { - ConfigurationEntry portEntry = _handler.getEntry(_portEntryId); - assertEquals("Unexpected entry id", _portEntryId, portEntry.getId()); - assertTrue("Unexpected children", portEntry.getChildrenIds().isEmpty()); - assertEquals("Unexpected state", State.QUIESCED, portEntry.getAttributes().get(Port.STATE)); - } - - public void testGetEntryByCLIConnectorPortId() - { - _options.setManagementModeJmxPortOverride(9090); - _handler = new ManagementModeStoreHandler(_store, _options); - - UUID optionsPort = getOptionsPortId(); - ConfigurationEntry portEntry = _handler.getEntry(optionsPort); - assertCLIPortEntry(portEntry, optionsPort, Protocol.JMX_RMI); - } - - public void testGetEntryByCLIHttpPortId() - { - _options.setManagementModeHttpPortOverride(9090); - _handler = new ManagementModeStoreHandler(_store, _options); - - UUID optionsPort = getOptionsPortId(); - ConfigurationEntry portEntry = _handler.getEntry(optionsPort); - assertCLIPortEntry(portEntry, optionsPort, Protocol.HTTP); - } - - public void testHttpPortEntryIsQuiesced() - { - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.HTTP)); - when(_portEntry.getAttributes()).thenReturn(attributes); - _options.setManagementModeHttpPortOverride(9090); - _handler = new ManagementModeStoreHandler(_store, _options); - - ConfigurationEntry portEntry = _handler.getEntry(_portEntryId); - assertEquals("Unexpected state", State.QUIESCED, portEntry.getAttributes().get(Port.STATE)); - } - - public void testRmiPortEntryIsQuiesced() - { - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.RMI)); - when(_portEntry.getAttributes()).thenReturn(attributes); - _options.setManagementModeRmiPortOverride(9090); - _handler = new ManagementModeStoreHandler(_store, _options); - - ConfigurationEntry portEntry = _handler.getEntry(_portEntryId); - assertEquals("Unexpected state", State.QUIESCED, portEntry.getAttributes().get(Port.STATE)); - } - - public void testConnectorPortEntryIsQuiesced() - { - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.JMX_RMI)); - when(_portEntry.getAttributes()).thenReturn(attributes); - _options.setManagementModeRmiPortOverride(9090); - _handler = new ManagementModeStoreHandler(_store, _options); - - ConfigurationEntry portEntry = _handler.getEntry(_portEntryId); - assertEquals("Unexpected state", State.QUIESCED, portEntry.getAttributes().get(Port.STATE)); - } - - public void testVirtualHostEntryIsNotQuiescedByDefault() - { - virtualHostEntryQuiescedStatusTestImpl(false); - } - - public void testVirtualHostEntryIsQuiescedWhenRequested() - { - virtualHostEntryQuiescedStatusTestImpl(true); - } - - private void virtualHostEntryQuiescedStatusTestImpl(boolean mmQuiesceVhosts) - { - UUID virtualHostId = UUID.randomUUID(); - ConfigurationEntry virtualHost = mock(ConfigurationEntry.class); - when(virtualHost.getId()).thenReturn(virtualHostId); - when(virtualHost.getType()).thenReturn(VirtualHost.class.getSimpleName()); - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(VirtualHost.CONFIG_PATH, "/path/to/host.xml"); - when(virtualHost.getAttributes()).thenReturn(attributes); - when(_store.getEntry(virtualHostId)).thenReturn(virtualHost); - when(_root.getChildrenIds()).thenReturn(new HashSet<UUID>(Arrays.asList(_portEntryId, virtualHostId))); - - State expectedState = mmQuiesceVhosts ? State.QUIESCED : null; - if(mmQuiesceVhosts) - { - _options.setManagementModeQuiesceVirtualHosts(mmQuiesceVhosts); - } - - _handler = new ManagementModeStoreHandler(_store, _options); - - ConfigurationEntry hostEntry = _handler.getEntry(virtualHostId); - Map<String, Object> hostAttributes = hostEntry.getAttributes(); - assertEquals("Unexpected state", expectedState, hostAttributes.get(VirtualHost.STATE)); - hostAttributes.remove(VirtualHost.STATE); - assertEquals("Unexpected attributes", attributes, hostAttributes); - } - - @SuppressWarnings("unchecked") - private void assertCLIPortEntry(ConfigurationEntry portEntry, UUID optionsPort, Protocol protocol) - { - assertEquals("Unexpected entry id", optionsPort, portEntry.getId()); - assertTrue("Unexpected children", portEntry.getChildrenIds().isEmpty()); - Map<String, Object> attributes = portEntry.getAttributes(); - assertEquals("Unexpected name", "MANAGEMENT-MODE-PORT-" + protocol.name(), attributes.get(Port.NAME)); - assertEquals("Unexpected protocol", Collections.singleton(protocol), new HashSet<Protocol>( - (Collection<Protocol>) attributes.get(Port.PROTOCOLS))); - } - - public void testSavePort() - { - _options.setManagementModeHttpPortOverride(1000); - _options.setManagementModeRmiPortOverride(2000); - _options.setManagementModeJmxPortOverride(3000); - _handler = new ManagementModeStoreHandler(_store, _options); - - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Port.NAME, "TEST"); - ConfigurationEntry configurationEntry = new ConfigurationEntry(_portEntryId, Port.class.getSimpleName(), attributes, - Collections.<UUID> emptySet(), null); - _handler.save(configurationEntry); - verify(_store).save(any(ConfigurationEntry.class)); - } - - public void testSaveRoot() - { - _options.setManagementModeHttpPortOverride(1000); - _options.setManagementModeRmiPortOverride(2000); - _options.setManagementModeJmxPortOverride(3000); - _handler = new ManagementModeStoreHandler(_store, _options); - - ConfigurationEntry root = _handler.getRootEntry(); - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Broker.NAME, "TEST"); - ConfigurationEntry configurationEntry = new ConfigurationEntry(_rootId, Broker.class.getSimpleName(), attributes, - root.getChildrenIds(), null); - _handler.save(configurationEntry); - verify(_store).save(any(ConfigurationEntry.class)); - } - - public void testSaveCLIHttpPort() - { - _options.setManagementModeHttpPortOverride(1000); - _handler = new ManagementModeStoreHandler(_store, _options); - - UUID portId = getOptionsPortId(); - Map<String, Object> attributes = new HashMap<String, Object>(); - attributes.put(Port.NAME, "TEST"); - ConfigurationEntry configurationEntry = new ConfigurationEntry(portId, Port.class.getSimpleName(), attributes, - Collections.<UUID> emptySet(), null); - try - { - _handler.save(configurationEntry); - fail("Exception should be thrown on trying to save CLI port"); - } - catch (IllegalConfigurationException e) - { - // pass - } - } - - public void testRemove() - { - _options.setManagementModeHttpPortOverride(1000); - _handler = new ManagementModeStoreHandler(_store, _options); - - _handler.remove(_portEntryId); - verify(_store).remove(_portEntryId); - } - - public void testRemoveCLIPort() - { - _options.setManagementModeHttpPortOverride(1000); - _handler = new ManagementModeStoreHandler(_store, _options); - UUID portId = getOptionsPortId(); - try - { - _handler.remove(portId); - fail("Exception should be thrown on trying to remove CLI port"); - } - catch (IllegalConfigurationException e) - { - // pass - } - } - - private UUID getOptionsPortId() - { - ConfigurationEntry root = _handler.getRootEntry(); - assertEquals("Unexpected root id", _rootId, root.getId()); - Collection<UUID> childrenIds = root.getChildrenIds(); - - childrenIds.remove(_portEntryId); - UUID optionsPort = childrenIds.iterator().next(); - return optionsPort; - } - -} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java index 508cd2b321..48c253d7e1 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java @@ -60,6 +60,12 @@ public class MemoryConfigurationEntryStoreTest extends ConfigurationEntryStoreTe store.save(newParentEntry, new ConfigurationEntry(id, type, attributes, Collections.<UUID> emptySet(), store)); } + @Override + protected ConfigurationEntryStore reOpenStore() + { + return getStore(); + } + public void testCreateWithNullLocationAndNullInitialStore() { try @@ -130,4 +136,5 @@ public class MemoryConfigurationEntryStoreTest extends ConfigurationEntryStoreTe { assertEquals("Unexpected type", "memory", getStore().getType()); } + } diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListenerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListenerTest.java index c23c4715e8..292e12f380 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListenerTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListenerTest.java @@ -33,6 +33,7 @@ import org.apache.qpid.server.configuration.ConfigurationEntryStore; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.test.utils.QpidTestCase; @@ -95,6 +96,28 @@ public class StoreConfigurationChangeListenerTest extends QpidTestCase verifyNoMoreInteractions(_store); } + public void testLocalReplicationNodeAddedForVirtualHost() + { + notifyBrokerStarted(); + + VirtualHost object = mock(VirtualHost.class); + ReplicationNode node = mock(ReplicationNode.class); + when(node.isLocal()).thenReturn(true); + _listener.childAdded(object, node); + verify(_store).save(any(ConfigurationEntry.class), any(ConfigurationEntry.class)); + } + + public void testRemoteReplicationNodeAddedForVirtualHost() + { + notifyBrokerStarted(); + + VirtualHost object = mock(VirtualHost.class); + ReplicationNode node = mock(ReplicationNode.class); + when(node.isLocal()).thenReturn(false); + _listener.childAdded(object, node); + verifyNoMoreInteractions(_store); + } + private void notifyBrokerStarted() { Broker broker = mock(Broker.class); diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/ConfiguredObjectStateTransitionTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/ConfiguredObjectStateTransitionTest.java index 1b7ef39b89..82ba20a4bd 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/ConfiguredObjectStateTransitionTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/ConfiguredObjectStateTransitionTest.java @@ -141,7 +141,7 @@ public class ConfiguredObjectStateTransitionTest extends QpidTestCase private void assertAllInvalidStateTransitions(ConfigurationEntry providerEntry) { ConfiguredObject provider = createConfiguredObject(providerEntry); - assertInvalidStateTransition(provider, State.INITIALISING, State.REPLICA); + assertInvalidStateTransition(provider, State.INITIALISING, State.UNAVAILABLE); provider.setDesiredState(State.INITIALISING, State.QUIESCED); assertInvalidStateTransition(provider, State.QUIESCED, State.INITIALISING); @@ -153,7 +153,7 @@ public class ConfiguredObjectStateTransitionTest extends QpidTestCase assertInvalidStateTransition(provider, State.DELETED, State.INITIALISING); assertInvalidStateTransition(provider, State.DELETED, State.QUIESCED); assertInvalidStateTransition(provider, State.DELETED, State.ACTIVE); - assertInvalidStateTransition(provider, State.DELETED, State.REPLICA); + assertInvalidStateTransition(provider, State.DELETED, State.UNAVAILABLE); assertInvalidStateTransition(provider, State.DELETED, State.ERRORED); } diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/VirtualHostTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/VirtualHostTest.java index 05ac4a1ec3..3a9d706754 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/VirtualHostTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/VirtualHostTest.java @@ -23,13 +23,12 @@ package org.apache.qpid.server.model; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.File; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; -import junit.framework.TestCase; - import org.apache.qpid.server.configuration.ConfigurationEntry; import org.apache.qpid.server.configuration.RecovererProvider; import org.apache.qpid.server.configuration.startup.VirtualHostRecoverer; @@ -42,6 +41,7 @@ import org.apache.qpid.server.store.TestMemoryMessageStore; import org.apache.qpid.server.util.BrokerTestHelper; import org.apache.qpid.server.virtualhost.StandardVirtualHostFactory; import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; public class VirtualHostTest extends QpidTestCase { @@ -49,6 +49,7 @@ public class VirtualHostTest extends QpidTestCase private Broker _broker; private StatisticsGatherer _statisticsGatherer; private RecovererProvider _recovererProvider; + private File _configFile; @Override protected void setUp() throws Exception @@ -68,6 +69,10 @@ public class VirtualHostTest extends QpidTestCase @Override protected void tearDown() throws Exception { + if (_configFile != null) + { + _configFile.delete(); + } super.tearDown(); CurrentActor.remove(); } @@ -76,15 +81,17 @@ public class VirtualHostTest extends QpidTestCase { VirtualHost host = createHost(); - assertEquals("Unexpected state", State.INITIALISING, host.getAttribute(VirtualHost.STATE)); + assertEquals("Unexpected state", State.INITIALISING, host.getActualState()); + assertEquals("Unexpected desired state", State.ACTIVE, host.getDesiredState()); } public void testActiveState() { VirtualHost host = createHost(); - host.setDesiredState(State.INITIALISING, State.ACTIVE); - assertEquals("Unexpected state", State.ACTIVE, host.getAttribute(VirtualHost.STATE)); + host.attainDesiredState(); + assertEquals("Unexpected state", State.ACTIVE, host.getActualState()); + assertEquals("Unexpected desired state", State.ACTIVE, host.getDesiredState()); } public void testQuiescedState() @@ -93,37 +100,64 @@ public class VirtualHostTest extends QpidTestCase attributes.put(VirtualHost.NAME, getName()); attributes.put(VirtualHost.TYPE, StandardVirtualHostFactory.TYPE); attributes.put(VirtualHost.STORE_TYPE, TestMemoryMessageStore.TYPE); - attributes.put(VirtualHost.STATE, State.QUIESCED); + attributes.put(VirtualHost.DESIRED_STATE, State.QUIESCED); VirtualHost host = createHost(attributes); + host.attainDesiredState(); - assertEquals("Unexpected state", State.QUIESCED, host.getAttribute(VirtualHost.STATE)); + assertEquals("Unexpected state", State.QUIESCED, host.getActualState()); + assertEquals("Unexpected desired state", State.QUIESCED, host.getDesiredState()); + } - host.setDesiredState(State.QUIESCED, State.ACTIVE); - assertEquals("Unexpected state", State.ACTIVE, host.getAttribute(VirtualHost.STATE)); + public void testChangeStateFromQuiescedToActiveViaAttributes() + { + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.TYPE, StandardVirtualHostFactory.TYPE); + attributes.put(VirtualHost.STORE_TYPE, TestMemoryMessageStore.TYPE); + attributes.put(VirtualHost.DESIRED_STATE, State.QUIESCED); + + VirtualHost host = createHost(attributes); + host.attainDesiredState(); + + assertEquals("Unexpected state", State.QUIESCED, host.getActualState()); + assertEquals("Unexpected desired state", State.QUIESCED, host.getDesiredState()); + + attributes = new HashMap<String, Object>(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.DESIRED_STATE, State.ACTIVE); + host.setAttributes(attributes); + assertEquals("Unexpected state", State.ACTIVE, host.getActualState()); + assertEquals("Unexpected desired state", State.ACTIVE, host.getDesiredState()); } - public void testStoppedState() + public void testChangeStateFromActiveToStoppedViaAttributes() { VirtualHost host = createHost(); + assertEquals("Unexpected state", State.INITIALISING, host.getActualState()); + assertEquals("Unexpected desired state", State.ACTIVE, host.getDesiredState()); - assertEquals("Unexpected state", State.INITIALISING, host.getAttribute(VirtualHost.STATE)); + host.attainDesiredState(); + assertEquals("Unexpected state", State.ACTIVE, host.getActualState()); + assertEquals("Unexpected desired state", State.ACTIVE, host.getDesiredState()); - host.setDesiredState(State.INITIALISING, State.ACTIVE); - assertEquals("Unexpected state", State.ACTIVE, host.getAttribute(VirtualHost.STATE)); + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.DESIRED_STATE, State.STOPPED); + host.setAttributes(attributes); - host.setDesiredState(State.ACTIVE, State.STOPPED); - assertEquals("Unexpected state", State.STOPPED, host.getAttribute(VirtualHost.STATE)); + assertEquals("Unexpected state", State.STOPPED, host.getActualState()); + assertEquals("Unexpected desired state", State.STOPPED, host.getDesiredState()); } public void testDeletedState() { VirtualHost host = createHost(); - assertEquals("Unexpected state", State.INITIALISING, host.getAttribute(VirtualHost.STATE)); + assertEquals("Unexpected state", State.INITIALISING, host.getActualState()); host.setDesiredState(State.INITIALISING, State.DELETED); - assertEquals("Unexpected state", State.DELETED, host.getAttribute(VirtualHost.STATE)); + assertEquals("Unexpected state", State.DELETED, host.getActualState()); } public void testCreateQueueChildHavingMessageGroupingAttributes() @@ -148,6 +182,18 @@ public class VirtualHostTest extends QpidTestCase } + public void testCreateVirtualHostFromConfigurationFile() + { + String hostName = getName(); + int maximuMessageAge = 123; + VirtualHost host = createHostFromConfiguration(hostName, maximuMessageAge); + host.setDesiredState(State.INITIALISING, State.ACTIVE); + assertEquals("Unexpected host name", hostName, host.getName()); + assertEquals("Unexpected host type", StandardVirtualHostFactory.TYPE, host.getType()); + assertEquals("Unexpected store type", TestMemoryMessageStore.TYPE, host.getAttribute(VirtualHost.STORE_TYPE)); + assertEquals("Unexpected maximum message age alert", maximuMessageAge, host.getAttribute(VirtualHost.QUEUE_ALERT_THRESHOLD_MESSAGE_AGE)); + } + private VirtualHost createHost() { Map<String, Object> attributes = new HashMap<String, Object>(); @@ -167,4 +213,18 @@ public class VirtualHostTest extends QpidTestCase return new VirtualHostRecoverer(_statisticsGatherer).create(_recovererProvider, entry, _broker); } + private VirtualHost createHostFromConfiguration(String hostName, long maximuMessageAge) + { + String content = "<virtualhosts><virtualhost><name>" + hostName + "</name><" + hostName + ">" + + "<queues><maximumMessageAge>" + maximuMessageAge + "</maximumMessageAge></queues>" + + "<store><class>" + TestMemoryMessageStore.class.getName() + "</class></store>" + + "</" + hostName + "></virtualhost></virtualhosts>"; + _configFile = TestFileUtils.createTempFile(this, ".virtualhost.xml", content); + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.CONFIG_PATH, _configFile.getAbsolutePath()); + return createHost(attributes); + } } + +
\ No newline at end of file diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/DurableConfigurationRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/DurableConfigurationRecovererTest.java index b71c6a92e6..9bce9398c1 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/DurableConfigurationRecovererTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/DurableConfigurationRecovererTest.java @@ -18,7 +18,7 @@ * under the License. * */ -package org.apache.qpid.server.virtualhost; +package org.apache.qpid.server.store; import java.util.Arrays; import java.util.Collections; @@ -46,6 +46,11 @@ import org.apache.qpid.server.store.ConfiguredObjectRecord; import org.apache.qpid.server.store.DurableConfigurationRecoverer; import org.apache.qpid.server.store.DurableConfigurationStore; import org.apache.qpid.server.store.DurableConfiguredObjectRecoverer; +import org.apache.qpid.server.virtualhost.BindingRecoverer; +import org.apache.qpid.server.virtualhost.DefaultUpgraderProvider; +import org.apache.qpid.server.virtualhost.ExchangeRecoverer; +import org.apache.qpid.server.virtualhost.QueueRecoverer; +import org.apache.qpid.server.virtualhost.VirtualHost; import org.apache.qpid.test.utils.QpidTestCase; import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; @@ -59,6 +64,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.same; import static org.apache.qpid.server.model.VirtualHost.CURRENT_CONFIG_VERSION; @@ -404,6 +410,32 @@ public class DurableConfigurationRecovererTest extends QpidTestCase assertEquals(customExchange, _vhost.getQueue(queueId).getAlternateExchange()); } + @SuppressWarnings("unchecked") + public void testRecovererCacheIsClearedAfterComplete() + { + String queueName = getName(); + UUID bindingId = new UUID(99, 99); + when(_directExchange.getBinding(same(queueName), any(AMQQueue.class), any(Map.class))).thenReturn(mock(org.apache.qpid.server.binding.Binding.class)); + + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, 2); + _durableConfigurationRecoverer.configuredObject(bindingId, Binding.class.getSimpleName(), createBinding(queueName, DIRECT_EXCHANGE_ID, QUEUE_ID)); + _durableConfigurationRecoverer.configuredObject(QUEUE_ID, Queue.class.getSimpleName(), createQueue(queueName, DIRECT_EXCHANGE_ID)); + + Object cachedBinding = _durableConfigurationRecoverer.getResolvedObject(Binding.class.getSimpleName(), bindingId); + assertNotNull("Binding is not cached:" + cachedBinding, cachedBinding); + + Object cachedQueue = _durableConfigurationRecoverer.getResolvedObject(Queue.class.getSimpleName(), QUEUE_ID); + assertNotNull("Queue is not cached:" + cachedQueue, cachedQueue); + + _durableConfigurationRecoverer.completeConfigurationRecovery(); + + cachedBinding = _durableConfigurationRecoverer.getResolvedObject(Binding.class.getSimpleName(), bindingId); + assertNull("Binding is cached:" + cachedBinding, cachedBinding); + + cachedQueue = _durableConfigurationRecoverer.getResolvedObject(Queue.class.getSimpleName(), QUEUE_ID); + assertNull("Queue is cached:" + cachedQueue, cachedQueue); + } + private void verifyCorrectUpdates(final ConfiguredObjectRecord[] expected) throws AMQStoreException { doAnswer(new Answer() diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreQuotaEventsTestBase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreQuotaEventsTestBase.java index 7a4f92f0ca..daf2640bba 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreQuotaEventsTestBase.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreQuotaEventsTestBase.java @@ -27,8 +27,10 @@ import java.util.List; import java.util.UUID; import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; import org.apache.qpid.server.message.EnqueueableMessage; import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler.StoredMessageRecoveryHandler; import org.apache.qpid.test.utils.QpidTestCase; import org.apache.qpid.util.FileUtils; @@ -71,8 +73,10 @@ public abstract class MessageStoreQuotaEventsTestBase extends QpidTestCase imple _store = createStore(); ((DurableConfigurationStore)_store).configureConfigStore(vhost, null); - _store.configureMessageStore(vhost, mock(MessageStoreRecoveryHandler.class), null); - + MessageStoreRecoveryHandler recoveryHandler = mock(MessageStoreRecoveryHandler.class); + when(recoveryHandler.begin()).thenReturn(mock(StoredMessageRecoveryHandler.class)); + _store.configureMessageStore(vhost, recoveryHandler, null); + _store.activate(); _transactionResource = UUID.randomUUID(); _events = new ArrayList<Event>(); _store.addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_OVERFULL, Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); @@ -118,7 +122,7 @@ public abstract class MessageStoreQuotaEventsTestBase extends QpidTestCase imple assertEvent(2, Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); } - protected EnqueueableMessage addMessage(long id) + protected EnqueueableMessage addMessage(long id) throws AMQStoreException { StorableMessageMetaData metaData = createMetaData(id, MESSAGE_DATA.length); StoredMessage<?> handle = _store.addMessage(metaData); diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/StateManagerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/StateManagerTest.java index 3ee98f9a21..16d18de713 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/StateManagerTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/StateManagerTest.java @@ -141,7 +141,7 @@ public class StateManagerTest extends TestCase implements EventListener performInvalidTransitions(StateManager.INITIALISE, State.INITIALISED); performInvalidTransitions(StateManager.INITIALISE_COMPLETE, State.ACTIVATING, State.CLOSING); - performInvalidTransitions(StateManager.ACTIVATE, State.ACTIVE); + performInvalidTransitions(StateManager.ACTIVATE, State.ACTIVE, State.CLOSING); performInvalidTransitions(StateManager.ACTIVATE_COMPLETE, State.QUIESCING, State.CLOSING, State.INITIALISED); performInvalidTransitions(StateManager.QUIESCE, State.QUIESCED); performInvalidTransitions(StateManager.QUIESCE_COMPLETE, State.ACTIVATING, State.CLOSING); diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/BrokerTestHelper.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/BrokerTestHelper.java index ed1ea01108..9671cd7a80 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/BrokerTestHelper.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/BrokerTestHelper.java @@ -111,6 +111,7 @@ public class BrokerTestHelper { virtualHostRegistry.registerVirtualHost(host); } + host.activate(); return host; } diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/HouseKeepingTaskTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/HouseKeepingTaskTest.java index 8b4a52bb79..e27598f035 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/HouseKeepingTaskTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/HouseKeepingTaskTest.java @@ -20,6 +20,9 @@ */ package org.apache.qpid.server.virtualhost; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import org.apache.qpid.server.logging.LogActor; import org.apache.qpid.server.logging.NullRootMessageLogger; import org.apache.qpid.server.logging.actors.CurrentActor; @@ -30,6 +33,16 @@ import java.util.concurrent.CountDownLatch; public class HouseKeepingTaskTest extends QpidTestCase { + private static final String HOUSE_KEEPING_TASK_TEST_VHOST = "HouseKeepingTaskTestVhost"; + private VirtualHost _host; + + public void setUp() throws Exception + { + super.setUp(); + _host = mock(VirtualHost.class); + when(_host.getName()).thenReturn(HOUSE_KEEPING_TASK_TEST_VHOST); + } + /** * Tests that the abstract HouseKeepingTask properly cleans up any LogActor * it adds to the CurrentActor stack by verifying the CurrentActor set @@ -45,7 +58,7 @@ public class HouseKeepingTaskTest extends QpidTestCase assertEquals("Expected LogActor was not returned", testActor, CurrentActor.get()); final CountDownLatch latch = new CountDownLatch(1); - HouseKeepingTask testTask = new HouseKeepingTask(new MockVirtualHost("HouseKeepingTaskTestVhost")) + HouseKeepingTask testTask = new HouseKeepingTask(_host) { @Override public void execute() @@ -73,11 +86,9 @@ public class HouseKeepingTaskTest extends QpidTestCase String originalThreadName = Thread.currentThread().getName(); - String vhostName = "HouseKeepingTaskTestVhost"; - - String expectedThreadNameDuringExecution = vhostName + ":" + "ThreadNameRememberingTask"; + String expectedThreadNameDuringExecution = HOUSE_KEEPING_TASK_TEST_VHOST + ":" + "ThreadNameRememberingTask"; - ThreadNameRememberingTask testTask = new ThreadNameRememberingTask(new MockVirtualHost(vhostName)); + ThreadNameRememberingTask testTask = new ThreadNameRememberingTask(_host); testTask.run(); diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/MockVirtualHost.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/MockVirtualHost.java deleted file mode 100644 index 832b89c81a..0000000000 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/MockVirtualHost.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.apache.qpid.server.virtualhost; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ScheduledFuture; -import org.apache.qpid.AMQException; -import org.apache.qpid.server.configuration.VirtualHostConfiguration; -import org.apache.qpid.server.connection.IConnectionRegistry; -import org.apache.qpid.server.exchange.Exchange; -import org.apache.qpid.server.message.MessageDestination; -import org.apache.qpid.server.message.MessageSource; -import org.apache.qpid.server.plugin.ExchangeType; -import org.apache.qpid.server.protocol.LinkRegistry; -import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.queue.QueueRegistry; -import org.apache.qpid.server.security.SecurityManager; -import org.apache.qpid.server.security.auth.manager.AuthenticationManager; -import org.apache.qpid.server.stats.StatisticsCounter; -import org.apache.qpid.server.store.DurableConfigurationStore; -import org.apache.qpid.server.store.MessageStore; -import org.apache.qpid.server.txn.DtxRegistry; - -import java.util.UUID; - -public class MockVirtualHost implements VirtualHost -{ - private String _name; - - public MockVirtualHost(String name) - { - _name = name; - } - - public void close() - { - - } - - @Override - public VirtualHostRegistry getVirtualHostRegistry() - { - return null; - } - - public AuthenticationManager getAuthenticationManager() - { - return null; - } - - public DtxRegistry getDtxRegistry() - { - return null; - } - - public VirtualHostConfiguration getConfiguration() - { - return null; - } - - public IConnectionRegistry getConnectionRegistry() - { - return null; - } - - public int getHouseKeepingActiveCount() - { - return 0; - } - - public long getHouseKeepingCompletedTaskCount() - { - return 0; - } - - public int getHouseKeepingPoolSize() - { - return 0; - } - - public long getHouseKeepingTaskCount() - { - return 0; - } - - public MessageStore getMessageStore() - { - return null; - } - - public DurableConfigurationStore getDurableConfigurationStore() - { - return null; - } - - public String getName() - { - return _name; - } - - public QueueRegistry getQueueRegistry() - { - return null; - } - - @Override - public AMQQueue getQueue(String name) - { - return null; - } - - @Override - public MessageSource getMessageSource(final String name) - { - return null; - } - - @Override - public AMQQueue getQueue(UUID id) - { - return null; - } - - @Override - public Collection<AMQQueue> getQueues() - { - return null; - } - - @Override - public int removeQueue(AMQQueue queue) throws AMQException - { - return 0; - } - - @Override - public AMQQueue createQueue(UUID id, - String queueName, - boolean durable, - String owner, - boolean autoDelete, - boolean exclusive, - boolean deleteOnNoConsumer, - Map<String, Object> arguments) throws AMQException - { - return null; - } - - @Override - public Exchange createExchange(UUID id, - String exchange, - String type, - boolean durable, - boolean autoDelete, - String alternateExchange) throws AMQException - { - return null; - } - - @Override - public void removeExchange(Exchange exchange, boolean force) throws AMQException - { - } - - @Override - public MessageDestination getMessageDestination(final String name) - { - return null; - } - - @Override - public Exchange getExchange(String name) - { - return null; - } - - @Override - public Exchange getExchange(UUID id) - { - return null; - } - - @Override - public Exchange getDefaultExchange() - { - return null; - } - - @Override - public Collection<Exchange> getExchanges() - { - return null; - } - - @Override - public Collection<ExchangeType<? extends Exchange>> getExchangeTypes() - { - return null; - } - - public SecurityManager getSecurityManager() - { - return null; - } - - @Override - public void addVirtualHostListener(VirtualHostListener listener) - { - } - - public LinkRegistry getLinkRegistry(String remoteContainerId) - { - return null; - } - - public ScheduledFuture<?> scheduleTask(long delay, Runnable timeoutTask) - { - return null; - } - - public void scheduleHouseKeepingTask(long period, HouseKeepingTask task) - { - - } - - public void setHouseKeepingPoolSize(int newSize) - { - - } - - - public long getCreateTime() - { - return 0; - } - - public UUID getId() - { - return null; - } - - public boolean isDurable() - { - return false; - } - - public StatisticsCounter getDataDeliveryStatistics() - { - return null; - } - - public StatisticsCounter getDataReceiptStatistics() - { - return null; - } - - public StatisticsCounter getMessageDeliveryStatistics() - { - return null; - } - - public StatisticsCounter getMessageReceiptStatistics() - { - return null; - } - - public void initialiseStatistics() - { - - } - - public void registerMessageDelivered(long messageSize) - { - - } - - public void registerMessageReceived(long messageSize, long timestamp) - { - - } - - public void resetStatistics() - { - - } - - public State getState() - { - return State.ACTIVE; - } - - public void block() - { - } - - public void unblock() - { - } -} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/StandardVirtualHostTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/StandardVirtualHostTest.java index f46349daa4..6699a756d5 100644 --- a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/StandardVirtualHostTest.java +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/StandardVirtualHostTest.java @@ -27,10 +27,8 @@ import static org.mockito.Mockito.when; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; - import org.apache.qpid.server.binding.Binding; import org.apache.qpid.server.configuration.VirtualHostConfiguration; - import org.apache.qpid.server.exchange.Exchange; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.queue.AMQQueue; @@ -266,10 +264,18 @@ public class StandardVirtualHostTest extends QpidTestCase _virtualHostRegistry = broker.getVirtualHostRegistry(); VirtualHostConfiguration configuration = new VirtualHostConfiguration(vhostName, config, broker); - VirtualHost host = new StandardVirtualHostFactory().createVirtualHost(_virtualHostRegistry, mock(StatisticsGatherer.class), new SecurityManager(mock(Broker.class), false), configuration, - mock(org.apache.qpid.server.model.VirtualHost.class)); - _virtualHostRegistry.registerVirtualHost(host); + return createVirtualHost(configuration); + } + private VirtualHost createVirtualHost(VirtualHostConfiguration configuration) throws Exception + { + org.apache.qpid.server.model.VirtualHost virtualHost = mock(org.apache.qpid.server.model.VirtualHost.class); + when(virtualHost.getAttribute(eq(org.apache.qpid.server.model.VirtualHost.STORE_TYPE))).thenReturn( + TestMemoryMessageStore.TYPE); + VirtualHost host = new StandardVirtualHostFactory().createVirtualHost(_virtualHostRegistry, + mock(StatisticsGatherer.class), new SecurityManager(mock(Broker.class), false), configuration, virtualHost); + _virtualHostRegistry.registerVirtualHost(host); + host.activate(); return host; } @@ -366,11 +372,6 @@ public class StandardVirtualHostTest extends QpidTestCase Configuration config = new PropertiesConfiguration(); VirtualHostConfiguration configuration = new VirtualHostConfiguration(virtualHostName, config, broker); - final org.apache.qpid.server.model.VirtualHost virtualHost = mock(org.apache.qpid.server.model.VirtualHost.class); - when(virtualHost.getAttribute(eq(org.apache.qpid.server.model.VirtualHost.STORE_TYPE))).thenReturn(TestMemoryMessageStore.TYPE); - VirtualHost host = new StandardVirtualHostFactory().createVirtualHost(_virtualHostRegistry, mock(StatisticsGatherer.class), new SecurityManager(mock(Broker.class), false), configuration, - virtualHost); - _virtualHostRegistry.registerVirtualHost(host); - return host; + return createVirtualHost(configuration); } } diff --git a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSessionDelegate.java b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSessionDelegate.java index 9a90b74656..d4fb2761a4 100644 --- a/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSessionDelegate.java +++ b/qpid/java/broker-plugins/amqp-0-10-protocol/src/main/java/org/apache/qpid/server/protocol/v0_10/ServerSessionDelegate.java @@ -392,7 +392,15 @@ public class ServerSessionDelegate extends SessionDelegate private StoredMessage<MessageMetaData_0_10> createStoreMessage(final MessageTransfer xfr, final MessageMetaData_0_10 messageMetaData, final MessageStore store) { - final StoredMessage<MessageMetaData_0_10> storeMessage = store.addMessage(messageMetaData); + StoredMessage<MessageMetaData_0_10> storeMessage; + try + { + storeMessage = store.addMessage(messageMetaData); + } + catch (AMQStoreException e) + { + throw new RuntimeException("Cannot store the message", e); + } ByteBuffer body = xfr.getBody(); if(body != null) { diff --git a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLink_1_0.java b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLink_1_0.java index 927972c8b2..df39ed667e 100644 --- a/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLink_1_0.java +++ b/qpid/java/broker-plugins/amqp-1-0-protocol/src/main/java/org/apache/qpid/server/protocol/v1_0/ReceivingLink_1_0.java @@ -26,6 +26,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; + +import org.apache.qpid.AMQStoreException; import org.apache.qpid.amqp_1_0.messaging.SectionDecoderImpl; import org.apache.qpid.amqp_1_0.transport.DeliveryStateHandler; import org.apache.qpid.amqp_1_0.transport.LinkEndpoint; @@ -148,7 +150,15 @@ public class ReceivingLink_1_0 implements ReceivingLinkListener, Link_1_0, Deliv _sectionDecoder, immutableSections); - StoredMessage<MessageMetaData_1_0> storedMessage = _vhost.getMessageStore().addMessage(mmd); + StoredMessage<MessageMetaData_1_0> storedMessage; + try + { + storedMessage = _vhost.getMessageStore().addMessage(mmd); + } + catch (AMQStoreException e) + { + throw new RuntimeException("Cannot store message", e); + } boolean skipping = true; int offset = 0; diff --git a/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStore.java b/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStore.java index bc8d157346..01b3890334 100644 --- a/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStore.java +++ b/qpid/java/broker-plugins/derby-store/src/main/java/org/apache/qpid/server/store/derby/DerbyMessageStore.java @@ -33,6 +33,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.store.AbstractJDBCMessageStore; import org.apache.qpid.server.store.DurableConfigurationStore; @@ -99,7 +100,7 @@ public class DerbyMessageStore extends AbstractJDBCMessageStore implements Messa return "bigint"; } - protected void doClose() throws SQLException + protected void doClose() throws AMQStoreException { try { @@ -117,7 +118,7 @@ public class DerbyMessageStore extends AbstractJDBCMessageStore implements Messa else { getLogger().error("Exception whilst shutting down the store: " + e); - throw e; + throw new AMQStoreException("Cannot close store", e); } } } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java index 3375a784ea..3992c707f4 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java @@ -67,6 +67,7 @@ import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.PreferencesProvider; import org.apache.qpid.server.model.Protocol; import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.server.model.Session; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.Transport; @@ -223,7 +224,12 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem int lastPort = -1; for (Port port : ports) { - if (State.QUIESCED.equals(port.getActualState())) + if (_logger.isDebugEnabled()) + { + _logger.debug("Http port " + port); + } + + if (State.ACTIVE != port.getActualState()) { continue; } @@ -326,6 +332,12 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem connector.setHost(bindingAddress.trim()); } connector.setPort(port.getPort()); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Added Jetty connector " + connector + " for port " + port); + } + server.addConnector(connector); } @@ -362,6 +374,7 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem addRestServlet(root, "truststore", TrustStore.class); addRestServlet(root, "plugin", Plugin.class); addRestServlet(root, "preferencesprovider", AuthenticationProvider.class, PreferencesProvider.class); + addRestServlet(root, "replicationnode", VirtualHost.class, ReplicationNode.class); root.addServlet(new ServletHolder(new UserPreferencesServlet()), "/rest/userpreferences/*"); root.addServlet(new ServletHolder(new LoggedOnUserPreferencesServlet()), "/rest/preferences"); @@ -523,4 +536,10 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem } } + @Override + public void close() + { + stop(); + } + } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js index ea3ba78372..8156961149 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js @@ -18,10 +18,11 @@ * under the License. * */ -define(["dojo/store/Memory", - "dojox/grid/DataGrid", - "dojo/data/ObjectStore", - "dojo/store/Observable"], function (Memory, DataGrid, ObjectStore, Observable) { +define(["qpid/common/util", + "dojo/store/Memory", + "dojox/grid/DataGrid", + "dojo/data/ObjectStore", + "dojo/store/Observable"], function (util, Memory, DataGrid, ObjectStore, Observable) { function UpdatableStore( data, divName, structure, func, props, Grid, notObservable ) { @@ -86,19 +87,13 @@ define(["dojo/store/Memory", if(data) { for(var i=0; i < data.length; i++) { if(theItem = store.get(data[i].id)) { - var modified; - for(var propName in data[i]) { - if(data[i].hasOwnProperty(propName)) { - if(theItem[ propName ] != data[i][ propName ]) { - theItem[ propName ] = data[i][ propName ]; - modified = true; - changed = true; - } - } - } + var modified = !util.equals(theItem, data[i]); if(modified) { - // ... check attributes for updates - store.notify(theItem, data[i].id); + store.put(data[i], {overwrite: true}); + if (store instanceof Observable) + { + store.notify(theItem, data[i].id); + } changed = true; } } else { diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/formatter.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/formatter.js index 2f8683ee1c..38255237db 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/formatter.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/formatter.js @@ -27,6 +27,10 @@ define(function () { var returnVal = { units: "B", value: "0"}; + if (!amount) + { + return returnVal; + } if(amount < 1000) { @@ -56,6 +60,10 @@ define(function () { { var returnVal = { units: "ms", value: "0"}; + if (!amount) + { + return returnVal; + } if(amount < 1000) { diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js index 3d349830ac..12068fa101 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js @@ -369,6 +369,94 @@ define(["dojo/_base/xhr", { alert(error); } + }; + + util.equals = function(object1, object2) + { + if (object1 && object2) + { + if (typeof object1 != typeof object2) + { + return false; + } + else + { + if (object1 instanceof Array || typeof object1 == "array") + { + if (object1.length != object2.length) + { + return false; + } + + for (var i = 0, l=object1.length; i < l; i++) + { + var item = object1[i]; + if (item && (item instanceof Array || typeof item == "array" || item instanceof Object)) + { + if (!this.equals(item, object2[i])) + { + return false; + } + } + else if (item != object2[i]) + { + return false; + } + } + + return true; + } + else if (object1 instanceof Object) + { + for (propName in object1) + { + if (object1.hasOwnProperty(propName) != object2.hasOwnProperty(propName)) + { + return false; + } + else if (typeof object1[propName] != typeof object2[propName]) + { + return false; + } + } + + for(propName in object2) + { + var object1Prop = object1[propName]; + var object2Prop = object2[propName]; + + if (object2.hasOwnProperty(propName) != object1.hasOwnProperty(propName)) + { + return false; + } + else if (typeof object1Prop != typeof object2Prop) + { + return false; + } + + if(!object2.hasOwnProperty(propName)) + { + // skip functions + continue; + } + + if (object1Prop && (object1Prop instanceof Array || typeof object1Prop == "array" || object1Prop instanceof Object)) + { + if (!this.equals(object1Prop, object2Prop)) + { + return false; + } + } + else if(object1Prop != object2Prop) + { + return false; + } + } + return true; + } + } + } + return object1 === object2; } return util; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js index e92fb4492e..c9723e0dfb 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js @@ -32,6 +32,7 @@ define(["dojo/_base/xhr", "qpid/management/addQueue", "qpid/management/addExchange", "dojox/grid/EnhancedGrid", + "dijit/form/ToggleButton", "dojo/domReady!"], function (xhr, parser, query, connect, registry, entities, properties, updater, util, formatter, UpdatableStore, addQueue, addExchange, EnhancedGrid) { @@ -63,8 +64,6 @@ define(["dojo/_base/xhr", updater.add( that.vhostUpdater ); - that.vhostUpdater.update(); - var addQueueButton = query(".addQueueButton", contentPane.containerNode)[0]; connect.connect(registry.byNode(addQueueButton), "onClick", function(evt){ addQueue.show(that.name) }); @@ -95,6 +94,23 @@ define(["dojo/_base/xhr", ); }}); + var vhostData = this.vhostUpdater.vhostData; + var virtualHostDetailsDiv = query(".virtualHostDetails", contentPane.containerNode)[0]; + require(["qpid/management/virtualhost/" + vhostData.type.toLowerCase() + "/show"], + function(VirtualHostDetails) { + try + { + that.vhostUpdater.details = new VirtualHostDetails(virtualHostDetailsDiv); + that.vhostUpdater.details.update(vhostData); + } + catch(e) + { + if (console && console.error) + { + console.error(e); + } + } + }); }; VirtualHost.prototype.close = function() { @@ -118,6 +134,7 @@ define(["dojo/_base/xhr", } storeNodes(["name", + "type", "state", "durable", "lifetimePolicy", @@ -136,15 +153,13 @@ define(["dojo/_base/xhr", "msgOutRate", "bytesOutRate", "bytesOutRateUnits", - "storeType", - "storePath", "configPath"]); this.query = "rest/virtualhost/"+ encodeURIComponent(vhost.name); var that = this; - xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) { + xhr.get({url: this.query, sync: true, handleAs: "json"}).then(function(data) { that.vhostData = data[0]; if (!that.vhostData.hasOwnProperty("configPath")) @@ -172,7 +187,7 @@ define(["dojo/_base/xhr", }}; that.updateHeader(); - that.queuesGrid = new UpdatableStore(that.vhostData.queues, findNode("queues"), + that.queuesGrid = new UpdatableStore(that.vhostData.queues || [], findNode("queues"), [ { name: "Name", field: "name", width: "90px"}, { name: "Messages", field: "queueDepthMessages", width: "90px"}, { name: "Arguments", field: "arguments", width: "100%"} @@ -188,7 +203,7 @@ define(["dojo/_base/xhr", }); } , gridProperties, EnhancedGrid); - that.exchangesGrid = new UpdatableStore(that.vhostData.exchanges, findNode("exchanges"), + that.exchangesGrid = new UpdatableStore(that.vhostData.exchanges || [], findNode("exchanges"), [ { name: "Name", field: "name", width: "120px"}, { name: "Type", field: "type", width: "120px"}, @@ -206,7 +221,7 @@ define(["dojo/_base/xhr", } , gridProperties, EnhancedGrid); - that.connectionsGrid = new UpdatableStore(that.vhostData.connections, + that.connectionsGrid = new UpdatableStore(that.vhostData.connections || [], findNode("connections"), [ { name: "Name", field: "name", width: "150px"}, { name: "User", field: "principal", width: "120px"}, @@ -232,9 +247,6 @@ define(["dojo/_base/xhr", controller.show("connection", connectionName, vhost, theItem.id); }); } ); - - - }); } @@ -242,150 +254,172 @@ define(["dojo/_base/xhr", Updater.prototype.updateHeader = function() { this.name.innerHTML = entities.encode(String(this.vhostData[ "name" ])); + this.type.innerHTML = entities.encode(String(this.vhostData[ "type" ])); this.state.innerHTML = entities.encode(String(this.vhostData[ "state" ])); this.durable.innerHTML = entities.encode(String(this.vhostData[ "durable" ])); this.lifetimePolicy.innerHTML = entities.encode(String(this.vhostData[ "lifetimePolicy" ])); - this.storeType.innerHTML = entities.encode(String(this.vhostData[ "storeType" ])); - this.storePath.innerHTML = entities.encode(String(this.vhostData[ "storePath" ])); this.configPath.innerHTML = entities.encode(String(this.vhostData[ "configPath" ])); }; Updater.prototype.update = function() { - var thisObj = this; xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) .then(function(data) { - thisObj.vhostData = data[0]; - util.flattenStatistics( thisObj.vhostData ); - var connections = thisObj.vhostData[ "connections" ]; - var queues = thisObj.vhostData[ "queues" ]; - var exchanges = thisObj.vhostData[ "exchanges" ]; + try + { + thisObj.performUpdate(data[0]); + } + catch(e) + { + if (console && console.error) + { + console.error(e); + } + } + }); + }; - thisObj.updateHeader(); + Updater.prototype.performUpdate = function(vhostData) + { + this.vhostData = vhostData; + util.flattenStatistics( this.vhostData ); + var connections = this.vhostData[ "connections" ]; + var queues = this.vhostData[ "queues" ]; + var exchanges = this.vhostData[ "exchanges" ]; + this.updateHeader(); - // update alerting info - var alertRepeatGap = formatter.formatTime( thisObj.vhostData["queue.alertRepeatGap"] ); + // update alerting info + var alertRepeatGap = formatter.formatTime( this.vhostData["queue.alertRepeatGap"] ); - thisObj.alertRepeatGap.innerHTML = alertRepeatGap.value; - thisObj.alertRepeatGapUnits.innerHTML = alertRepeatGap.units; + this.alertRepeatGap.innerHTML = alertRepeatGap.value; + this.alertRepeatGapUnits.innerHTML = alertRepeatGap.units; - var alertMsgAge = formatter.formatTime( thisObj.vhostData["queue.alertThresholdMessageAge"] ); + var alertMsgAge = formatter.formatTime( this.vhostData["queue.alertThresholdMessageAge"] ); - thisObj.alertThresholdMessageAge.innerHTML = alertMsgAge.value; - thisObj.alertThresholdMessageAgeUnits.innerHTML = alertMsgAge.units; + this.alertThresholdMessageAge.innerHTML = alertMsgAge.value; + this.alertThresholdMessageAgeUnits.innerHTML = alertMsgAge.units; - var alertMsgSize = formatter.formatBytes( thisObj.vhostData["queue.alertThresholdMessageSize"] ); + var alertMsgSize = formatter.formatBytes( this.vhostData["queue.alertThresholdMessageSize"] ); - thisObj.alertThresholdMessageSize.innerHTML = alertMsgSize.value; - thisObj.alertThresholdMessageSizeUnits.innerHTML = alertMsgSize.units; + this.alertThresholdMessageSize.innerHTML = alertMsgSize.value; + this.alertThresholdMessageSizeUnits.innerHTML = alertMsgSize.units; - var alertQueueDepth = formatter.formatBytes( thisObj.vhostData["queue.alertThresholdQueueDepthBytes"] ); + var alertQueueDepth = formatter.formatBytes( this.vhostData["queue.alertThresholdQueueDepthBytes"] ); - thisObj.alertThresholdQueueDepthBytes.innerHTML = alertQueueDepth.value; - thisObj.alertThresholdQueueDepthBytesUnits.innerHTML = alertQueueDepth.units; + this.alertThresholdQueueDepthBytes.innerHTML = alertQueueDepth.value; + this.alertThresholdQueueDepthBytesUnits.innerHTML = alertQueueDepth.units; - thisObj.alertThresholdQueueDepthMessages.innerHTML = entities.encode(String(thisObj.vhostData["queue.alertThresholdQueueDepthMessages"])); + this.alertThresholdQueueDepthMessages.innerHTML = entities.encode(String(this.vhostData["queue.alertThresholdQueueDepthMessages"])); - var stats = thisObj.vhostData[ "statistics" ]; + var stats = this.vhostData[ "statistics" ] || {}; - var sampleTime = new Date(); - var messageIn = stats["messagesIn"]; - var bytesIn = stats["bytesIn"]; - var messageOut = stats["messagesOut"]; - var bytesOut = stats["bytesOut"]; + var sampleTime = new Date(); + var messageIn = stats["messagesIn"]; + var bytesIn = stats["bytesIn"]; + var messageOut = stats["messagesOut"]; + var bytesOut = stats["bytesOut"]; - if(thisObj.sampleTime) - { - var samplePeriod = sampleTime.getTime() - thisObj.sampleTime.getTime(); - - var msgInRate = (1000 * (messageIn - thisObj.messageIn)) / samplePeriod; - var msgOutRate = (1000 * (messageOut - thisObj.messageOut)) / samplePeriod; - var bytesInRate = (1000 * (bytesIn - thisObj.bytesIn)) / samplePeriod; - var bytesOutRate = (1000 * (bytesOut - thisObj.bytesOut)) / samplePeriod; - - thisObj.msgInRate.innerHTML = msgInRate.toFixed(0); - var bytesInFormat = formatter.formatBytes( bytesInRate ); - thisObj.bytesInRate.innerHTML = "(" + bytesInFormat.value; - thisObj.bytesInRateUnits.innerHTML = bytesInFormat.units + "/s)"; - - thisObj.msgOutRate.innerHTML = msgOutRate.toFixed(0); - var bytesOutFormat = formatter.formatBytes( bytesOutRate ); - thisObj.bytesOutRate.innerHTML = "(" + bytesOutFormat.value; - thisObj.bytesOutRateUnits.innerHTML = bytesOutFormat.units + "/s)"; - - if(connections && thisObj.connections) - { - for(var i=0; i < connections.length; i++) - { - var connection = connections[i]; - for(var j = 0; j < thisObj.connections.length; j++) - { - var oldConnection = thisObj.connections[j]; - if(oldConnection.id == connection.id) - { - msgOutRate = (1000 * (connection.messagesOut - oldConnection.messagesOut)) / - samplePeriod; - connection.msgOutRate = msgOutRate.toFixed(0) + "msg/s"; - - bytesOutRate = (1000 * (connection.bytesOut - oldConnection.bytesOut)) / - samplePeriod; - var bytesOutRateFormat = formatter.formatBytes( bytesOutRate ); - connection.bytesOutRate = bytesOutRateFormat.value + bytesOutRateFormat.units + "/s"; - - - msgInRate = (1000 * (connection.messagesIn - oldConnection.messagesIn)) / - samplePeriod; - connection.msgInRate = msgInRate.toFixed(0) + "msg/s"; - - bytesInRate = (1000 * (connection.bytesIn - oldConnection.bytesIn)) / - samplePeriod; - var bytesInRateFormat = formatter.formatBytes( bytesInRate ); - connection.bytesInRate = bytesInRateFormat.value + bytesInRateFormat.units + "/s"; - } - - - } - - } - } - } + if(this.sampleTime) + { + var samplePeriod = sampleTime.getTime() - this.sampleTime.getTime(); - thisObj.sampleTime = sampleTime; - thisObj.messageIn = messageIn; - thisObj.bytesIn = bytesIn; - thisObj.messageOut = messageOut; - thisObj.bytesOut = bytesOut; - thisObj.connections = connections; + var msgInRate = (1000 * (messageIn - this.messageIn)) / samplePeriod; + var msgOutRate = (1000 * (messageOut - this.messageOut)) / samplePeriod; + var bytesInRate = (1000 * (bytesIn - this.bytesIn)) / samplePeriod; + var bytesOutRate = (1000 * (bytesOut - this.bytesOut)) / samplePeriod; - // update queues - thisObj.queuesGrid.update(thisObj.vhostData.queues); + this.msgInRate.innerHTML = msgInRate.toFixed(0); + var bytesInFormat = formatter.formatBytes( bytesInRate ); + this.bytesInRate.innerHTML = "(" + bytesInFormat.value; + this.bytesInRateUnits.innerHTML = bytesInFormat.units + "/s)"; - // update exchanges - thisObj.exchangesGrid.update(thisObj.vhostData.exchanges); + this.msgOutRate.innerHTML = msgOutRate.toFixed(0); + var bytesOutFormat = formatter.formatBytes( bytesOutRate ); + this.bytesOutRate.innerHTML = "(" + bytesOutFormat.value; + this.bytesOutRateUnits.innerHTML = bytesOutFormat.units + "/s)"; - var exchangesGrid = thisObj.exchangesGrid.grid; - for(var i=0; i< thisObj.vhostData.exchanges.length; i++) - { - var data = exchangesGrid.getItem(i); - var isStandard = false; - if (data && data.name) - { - isStandard = util.isReservedExchangeName(data.name); - } - exchangesGrid.rowSelectCell.setDisabled(i, isStandard); - } + if(connections && this.connections) + { + for(var i=0; i < connections.length; i++) + { + var connection = connections[i]; + for(var j = 0; j < this.connections.length; j++) + { + var oldConnection = this.connections[j]; + if(oldConnection.id == connection.id) + { + msgOutRate = (1000 * (connection.messagesOut - oldConnection.messagesOut)) / + samplePeriod; + connection.msgOutRate = msgOutRate.toFixed(0) + "msg/s"; - // update connections - thisObj.connectionsGrid.update(thisObj.vhostData.connections) + bytesOutRate = (1000 * (connection.bytesOut - oldConnection.bytesOut)) / + samplePeriod; + var bytesOutRateFormat = formatter.formatBytes( bytesOutRate ); + connection.bytesOutRate = bytesOutRateFormat.value + bytesOutRateFormat.units + "/s"; - }); - }; + msgInRate = (1000 * (connection.messagesIn - oldConnection.messagesIn)) / + samplePeriod; + connection.msgInRate = msgInRate.toFixed(0) + "msg/s"; + + bytesInRate = (1000 * (connection.bytesIn - oldConnection.bytesIn)) / + samplePeriod; + var bytesInRateFormat = formatter.formatBytes( bytesInRate ); + connection.bytesInRate = bytesInRateFormat.value + bytesInRateFormat.units + "/s"; + } + + + } + + } + } + } + this.sampleTime = sampleTime; + this.messageIn = messageIn; + this.bytesIn = bytesIn; + this.messageOut = messageOut; + this.bytesOut = bytesOut; + this.connections = connections; + + // update queues + if (this.vhostData.queues) + { + this.queuesGrid.update(this.vhostData.queues); + } + + // update exchanges + if (this.vhostData.exchanges) + { + this.exchangesGrid.update(this.vhostData.exchanges); + var exchangesGrid = this.exchangesGrid.grid; + for(var i=0; i< this.vhostData.exchanges.length; i++) + { + var data = exchangesGrid.getItem(i); + var isStandard = false; + if (data && data.name) + { + isStandard = util.isReservedExchangeName(data.name); + } + exchangesGrid.rowSelectCell.setDisabled(i, isStandard); + } + } + + // update connections + if (this.vhostData.connections) + { + this.connectionsGrid.update(this.vhostData.connections); + } + + if (this.details) + { + this.details.update(this.vhostData); + } + } return VirtualHost; }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addVirtualHost.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addVirtualHost.js index 4873baeef7..b4db56b4e1 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addVirtualHost.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addVirtualHost.js @@ -123,15 +123,27 @@ define(["dojo/_base/xhr", alert("Either configuration file or type have to be specified!"); return false; } - var newVirtualHost = convertToVirtualHost(formValues); - var that = this; - xhr.put({url: "rest/virtualhost/" + encodeURIComponent(newVirtualHost.name), - sync: true, handleAs: "json", - headers: { "Content-Type": "application/json"}, - putData: json.toJson(newVirtualHost), - load: function(x) {that.success = true; }, - error: function(error) {that.success = false; that.failureReason = error;}}); + if (addVirtualHost.typeSpecificWidget && addVirtualHost.typeSpecificWidget.save + && typeof addVirtualHost.typeSpecificWidget.save == "function") + { + this.success = addVirtualHost.typeSpecificWidget.save(); + if (!this.success) + { + this.failureReason = addVirtualHost.failureReason + } + } + else + { + var that = this; + var newVirtualHost = convertToVirtualHost(formValues); + xhr.put({url: "rest/virtualhost/" + encodeURIComponent(newVirtualHost.name), + sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.toJson(newVirtualHost), + load: function(){ that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + } if(this.success === true) { @@ -156,12 +168,14 @@ define(["dojo/_base/xhr", function(vhostType) { vhostType.show(); + addVirtualHost.typeSpecificWidget = vhostType; }); } } addVirtualHost.show = function() { var that = this; + this.typeSpecificWidget = null; dom.byId("addVirtualHost.typeSpecificDiv").innerHTML = ""; registry.byId("formAddVirtualHost").reset(); dojo.byId("formAddVirtualHost.id").value=""; diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/virtualhost/standard/show.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/virtualhost/standard/show.js new file mode 100644 index 0000000000..4970936b63 --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/virtualhost/standard/show.js @@ -0,0 +1,100 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/string", + "dojox/html/entities", + "dojo/query", + "dijit/registry", + "dojox/grid/EnhancedGrid", + "qpid/common/UpdatableStore", + "qpid/common/formatter", + "dojo/domReady!"], + function (xhr, parser, json, entities, query, registry, EnhancedGrid, UpdatableStore, formatter) { + + var fields = ["storePath", "storeType", "desiredState"]; + + var buttons = []; + + function findNode(nodeClass, containerNode) + { + return query("." + nodeClass, containerNode)[0]; + } + + function Standard(containerNode) { + var that = this; + xhr.get({url: "virtualhost/standard/show.html", + sync: true, + load: function(template) { + that._init(template, containerNode); + }}); + } + + Standard.prototype.update=function(data) + { + for(var i = 0; i < fields.length; i++) + { + var name = fields[i]; + this[name].innerHTML = entities.encode(String(data[name])); + } + }; + + Standard.prototype._init = function(template, containerNode) + { + containerNode.innerHTML = template; + parser.parse(containerNode); + this._initFields(fields, containerNode); + for(var i = 0; i < buttons.length; i++) + { + var buttonName = buttons[i]; + var buttonNode = findNode(buttonName, containerNode); + if (buttonNode) + { + var buttonWidget = registry.byNode(buttonNode); + if (buttonWidget) + { + this[buttonName] = buttonWidget; + var handler = this["_onClick_" + buttonName]; + if (handler) + { + buttonWidget.on("click", function(evt){ + handler(evt); + }); + } + else + { + //buttonWidget.set("disabled", true); + } + } + } + } + } + + Standard.prototype._initFields = function(fields, containerNode) + { + for(var i = 0; i < fields.length; i++) + { + this[fields[i]] = findNode(fields[i], containerNode); + } + } + + return Standard; +}); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html index 69856365db..a0142d77c0 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html @@ -21,103 +21,85 @@ --> <div class="virtualhost"> - <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Virtual Host Attributes', open: true"> - <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 100px;">Name:</div> - <div class="name" style="float:left;"></div> - </div> - <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 100px;">State:</div> - <div class="state" style="float:left;"></div> - </div> - <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 100px;">Durable:</div> - <div class="durable" style="float:left;"></div> - </div> - <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 100px;">Lifespan:</div> - <div class="lifetimePolicy" style="float:left;"></div> - </div> - <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 100px;">Inbound:</div> - <div style="float:left;"> - <span class="msgInRate"></span> - <span> msg/s</span> - <span class="bytesInRate"></span> - <span class="bytesInRateUnits"></span> - </div> - </div> - <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 100px;">Outbound:</div> - <div style="float:left;"> - <span class="msgOutRate"></span> - <span> msg/s</span> - <span class="bytesOutRate"></span> - <span class="bytesOutRateUnits"></span> - </div> - </div> - <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 100px;">Store Type:</div> - <div class="storeType" style="float:left;"></div> - </div> - <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 100px;">Store Path:</div> - <div class="storePath" style="float:left;"></div> - </div> - <div class="configPathDiv" style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 100px;">Config Path:</div> - <div class="configPath" style="float:left;"></div> + + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Name:</div> + <div class="name" style="float:left;"></div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Type:</div> + <div class="type" style="float:left;"></div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Actual State:</div> + <div class="state" style="float:left;"></div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Durable:</div> + <div class="durable" style="float:left;"></div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Lifespan:</div> + <div class="lifetimePolicy" style="float:left;"></div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Inbound:</div> + <div style="float:left;"> + <span class="msgInRate"></span> + <span> msg/s</span> + <span class="bytesInRate"></span> + <span class="bytesInRateUnits"></span> </div> - <div style="clear:both"></div> - </div> - <br/> - <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Exchanges'"> - <div class="exchanges"></div> - <button data-dojo-type="dijit.form.Button" class="addExchangeButton">Add Exchange</button> - <button data-dojo-type="dijit.form.Button" class="deleteExchangeButton">Delete Exchange</button> </div> - <br/> - <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Queues'"> - <div class="queues"></div> - <button data-dojo-type="dijit.form.Button" class="addQueueButton">Add Queue</button> - <button data-dojo-type="dijit.form.Button" class="deleteQueueButton">Delete Queue</button> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Outbound:</div> + <div style="float:left;"> + <span class="msgOutRate"></span> + <span> msg/s</span> + <span class="bytesOutRate"></span> + <span class="bytesOutRateUnits"></span> + </div> </div> - <br/> - <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Connections'"> - <div class="connections"></div> + <div class="configPathDiv" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Config Path:</div> + <div class="configPath" style="float:left;"></div> </div> + <div style="clear:both"></div> + + <div class="virtualHostDetails"></div> <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Alerting Thresholds', open: false"> <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 150px;">Queue Depth:</div> + <div class="formLabel-labelCell" style="float:left; width: 200px">Queue Depth:</div> <div style="float:left;"> <span class="alertThresholdQueueDepthMessages"></span> <span>msgs</span> </div> </div> <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 150px;">Queue Depth:</div> + <div class="formLabel-labelCell" style="float:left; width: 200px">Queue Depth:</div> <div style="float:left;"> <span class="alertThresholdQueueDepthBytes"></span> <span class="alertThresholdQueueDepthBytesUnits"></span> </div> </div> <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 150px;">Message Age:</div> + <div class="formLabel-labelCell" style="float:left; width: 200px">Message Age:</div> <div style="float:left;"> <span class="alertThresholdMessageAge"></span> <span class="alertThresholdMessageAgeUnits"></span> </div> </div> <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 150px;">Message Size:</div> + <div class="formLabel-labelCell" style="float:left; width: 200px">Message Size:</div> <div style="float:left;"> <span class="alertThresholdMessageSize"></span> <span class="alertThresholdMessageSizeUnits"></span> </div> </div> <div style="clear:both"> - <div class="formLabel-labelCell" style="float:left; width: 150px;">Alert frequency:</div> + <div class="formLabel-labelCell" style="float:left; width: 200px">Alert frequency:</div> <div style="float:left;"> <span class="alertRepeatGap"></span> <span class="alertRepeatGapUnits"></span> @@ -125,5 +107,23 @@ </div> <div style="clear:both"></div> </div> + <br/> + + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Exchanges'"> + <div class="exchanges"></div> + <button data-dojo-type="dijit.form.Button" class="addExchangeButton">Add Exchange</button> + <button data-dojo-type="dijit.form.Button" class="deleteExchangeButton">Delete Exchange</button> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Queues'"> + <div class="queues"></div> + <button data-dojo-type="dijit.form.Button" class="addQueueButton">Add Queue</button> + <button data-dojo-type="dijit.form.Button" class="deleteQueueButton">Delete Queue</button> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Connections'"> + <div class="connections"></div> + </div> + </div> diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/virtualhost/standard/show.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/virtualhost/standard/show.html new file mode 100644 index 0000000000..85040cdefb --- /dev/null +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/virtualhost/standard/show.html @@ -0,0 +1,35 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you 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. + - + --> +<div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Desired State:</div> + <div class="desiredState" style="float:left;">ACTIVE</div> + </div> + <div style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Store Type:</div> + <div class="storeType" style="float:left;"></div> + </div> + <div style="clear:both;"> + <div class="formLabel-labelCell" style="float:left; width: 200px">Store Path:</div> + <div class="storePath" style="float:left;"></div> + </div> + <div style="clear:both"></div> +</div> diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagement.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagement.java index 18e9f9f809..44421c721a 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagement.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagement.java @@ -35,6 +35,7 @@ import javax.management.JMException; import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.jmx.mbeans.LoggingManagementMBean; +import org.apache.qpid.server.jmx.mbeans.MBeanUtils; import org.apache.qpid.server.jmx.mbeans.UserManagementMBean; import org.apache.qpid.server.jmx.mbeans.ServerInformationMBean; import org.apache.qpid.server.jmx.mbeans.Shutdown; @@ -52,7 +53,6 @@ import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.model.adapter.AbstractPluginAdapter; import org.apache.qpid.server.plugin.PluginFactory; -import org.apache.qpid.server.plugin.QpidServiceLoader; import org.apache.qpid.server.util.MapValueConverter; public class JMXManagement extends AbstractPluginAdapter implements ConfigurationChangeListener @@ -130,7 +130,12 @@ public class JMXManagement extends AbstractPluginAdapter implements Configuratio Collection<Port> ports = broker.getPorts(); for (Port port : ports) { - if (State.QUIESCED.equals(port.getActualState())) + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Port " + port); + } + + if (State.ACTIVE != port.getActualState()) { continue; } @@ -153,6 +158,11 @@ public class JMXManagement extends AbstractPluginAdapter implements Configuratio throw new IllegalStateException("No JMX RMI port found supporting protocol " + Protocol.RMI); } + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Found connector port " + connectorPort + " found registry port " + registryPort); + } + _objectRegistry = new JMXManagedObjectRegistry(broker, connectorPort, registryPort, this); broker.addChangeListener(this); @@ -173,7 +183,9 @@ public class JMXManagement extends AbstractPluginAdapter implements Configuratio { LOGGER.debug("Check for additional MBeans for virtual host:" + virtualHost.getName()); } - createAdditionalMBeansFromProviders(virtualHost, mbean); + + _children.put(virtualHost, mbean); + MBeanUtils.createAdditionalMBeansFromProviders(virtualHost, mbean); } } Collection<AuthenticationProvider> authenticationProviders = broker.getAuthenticationProviders(); @@ -263,7 +275,10 @@ public class JMXManagement extends AbstractPluginAdapter implements Configuratio if (mbean != null) { - createAdditionalMBeansFromProviders(child, mbean); + _children.put(child, mbean); + MBeanUtils.createAdditionalMBeansFromProviders(child, mbean); + // TODO track the mbeans that have been created on behalf of a child in a map, then + // if the child is ever removed, destroy these beans too. } } catch(Exception e) @@ -301,31 +316,6 @@ public class JMXManagement extends AbstractPluginAdapter implements Configuratio // no-op } - private void createAdditionalMBeansFromProviders(ConfiguredObject child, AMQManagedObject mbean) throws JMException - { - _children.put(child, mbean); - - QpidServiceLoader<MBeanProvider> qpidServiceLoader = new QpidServiceLoader<MBeanProvider>(); - for (MBeanProvider provider : qpidServiceLoader.instancesOf(MBeanProvider.class)) - { - if(LOGGER.isDebugEnabled()) - { - LOGGER.debug("Consulting mbean provider : " + provider + " for child : " + child); - } - - if (provider.isChildManageableByMBean(child)) - { - if(LOGGER.isDebugEnabled()) - { - LOGGER.debug("Provider will create mbean"); - } - provider.createMBean(child, mbean); - // TODO track the mbeans that have been created on behalf of a child in a map, then - // if the child is ever removed, destroy these beans too. - } - } - } - @Override public String getName() { @@ -373,4 +363,10 @@ public class JMXManagement extends AbstractPluginAdapter implements Configuratio } } } + + @Override + public void close() + { + stop(); + } } diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanProvider.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanProvider.java index a0ef052314..941aac1a2a 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanProvider.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanProvider.java @@ -22,7 +22,6 @@ package org.apache.qpid.server.jmx; import javax.management.JMException; -import javax.management.StandardMBean; import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.plugin.Pluggable; @@ -47,6 +46,6 @@ public interface MBeanProvider extends Pluggable * * @return newly created mbean */ - StandardMBean createMBean(ConfiguredObject child, StandardMBean parent) throws JMException; + ManagedObject createMBean(ConfiguredObject child, ManagedObject parent) throws JMException; } diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/MBeanUtils.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/MBeanUtils.java index 97e84d4796..1ddcae33a8 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/MBeanUtils.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/MBeanUtils.java @@ -20,15 +20,28 @@ */ package org.apache.qpid.server.jmx.mbeans; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.management.JMException; import javax.management.OperationsException; +import org.apache.log4j.Logger; +import org.apache.qpid.server.jmx.AMQManagedObject; +import org.apache.qpid.server.jmx.MBeanProvider; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.model.ConfiguredObjectFinder; import org.apache.qpid.server.model.Exchange; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.plugin.QpidServiceLoader; public class MBeanUtils { + private static final Logger LOGGER = Logger.getLogger(MBeanUtils.class); + public static Queue findQueueFromQueueName(VirtualHost virtualHost, String queueName) throws OperationsException { Queue queue = ConfiguredObjectFinder.findConfiguredObjectByName(virtualHost.getQueues(), queueName); @@ -54,4 +67,24 @@ public class MBeanUtils return exchange; } } + + public static Collection<ManagedObject> createAdditionalMBeansFromProviders(ConfiguredObject child, AMQManagedObject mbean) throws JMException + { + List<ManagedObject> mbeans = new ArrayList<ManagedObject>(); + QpidServiceLoader<MBeanProvider> qpidServiceLoader = new QpidServiceLoader<MBeanProvider>(); + for (MBeanProvider provider : qpidServiceLoader.instancesOf(MBeanProvider.class)) + { + if (provider.isChildManageableByMBean(child)) + { + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Provider of type " + provider.getType() + " will create MBean for child : " + child); + } + ManagedObject childMBean = provider.createMBean(child, mbean); + mbeans.add(childMBean); + } + } + return mbeans; + } + } diff --git a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java index e9e3e1df49..fa907552fd 100644 --- a/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java +++ b/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java @@ -21,6 +21,14 @@ package org.apache.qpid.server.jmx.mbeans; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.management.JMException; +import javax.management.ObjectName; + import org.apache.log4j.Logger; import org.apache.qpid.server.jmx.AMQManagedObject; import org.apache.qpid.server.jmx.ManagedObject; @@ -29,26 +37,26 @@ import org.apache.qpid.server.model.ConfigurationChangeListener; import org.apache.qpid.server.model.ConfiguredObject; import org.apache.qpid.server.model.Connection; import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.Model; import org.apache.qpid.server.model.Queue; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.virtualhost.ManagedVirtualHost; -import javax.management.JMException; -import javax.management.ObjectName; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtualHost, ConfigurationChangeListener { private static final Logger LOGGER = Logger.getLogger(VirtualHostMBean.class); private final VirtualHost _virtualHost; + private final Object _childrenLock = new Object(); + private final Map<ConfiguredObject, AMQManagedObject> _children = new HashMap<ConfiguredObject, AMQManagedObject>(); + + private final Map<ConfiguredObject, Collection<ManagedObject>> _additionalChildren = + new HashMap<ConfiguredObject, Collection<ManagedObject>>(); + private VirtualHostManagerMBean _managerMBean; public VirtualHostMBean(VirtualHost virtualHost, ManagedObjectRegistry registry) throws JMException @@ -61,13 +69,48 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual initExchanges(); initConnections(); + initAdditionalMbeansForAllChildren(); + //This is the actual JMX bean for this 'VirtualHostMBean', leave it alone. _managerMBean = new VirtualHostManagerMBean(this); } + private void initAdditionalMbeansForAllChildren() + { + synchronized (_childrenLock) + { + Collection<Class<? extends ConfiguredObject>> childrenTypes = Model.getInstance().getChildTypes(VirtualHost.class); + for (Class<? extends ConfiguredObject> childType : childrenTypes) + { + Collection<? extends ConfiguredObject> children = _virtualHost.getChildren(childType); + for (ConfiguredObject child : children) + { + createAdditionalMBeans(child); + } + } + } + } + + private void createAdditionalMBeans(ConfiguredObject child) + { + try + { + Collection<ManagedObject> mbeans = MBeanUtils.createAdditionalMBeansFromProviders(child, this); + if (!mbeans.isEmpty()) + { + _additionalChildren.put(child, mbeans); + } + } + catch(Exception e) + { + LOGGER.error("Cannot create mbeans for the child " + child.getName(), e); + } + } + + private void initQueues() { - synchronized (_children) + synchronized (_childrenLock) { for(Queue queue : _virtualHost.getQueues()) { @@ -88,7 +131,7 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual private void initExchanges() { - synchronized (_children) + synchronized (_childrenLock) { for(Exchange exchange : _virtualHost.getExchanges()) { @@ -109,7 +152,7 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual private void initConnections() { - synchronized (_children) + synchronized (_childrenLock) { for(Connection conn : _virtualHost.getConnections()) { @@ -145,7 +188,7 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual public void childAdded(ConfiguredObject object, ConfiguredObject child) { - synchronized (_children) + synchronized (_childrenLock) { try { @@ -153,36 +196,29 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual { QueueMBean queueMB = new QueueMBean((Queue)child, this); _children.put(child, queueMB); - } else if(child instanceof Exchange) { ExchangeMBean exchangeMBean = new ExchangeMBean((Exchange)child, this); _children.put(child, exchangeMBean); - } else if(child instanceof Connection) { ConnectionMBean connectionMBean = new ConnectionMBean((Connection)child, this); _children.put(child, connectionMBean); - - } - else - { - LOGGER.debug("Unsupported child : " + child.getName() + " type : " + child.getClass()); } - } catch(Exception e) { LOGGER.error("Exception while creating mbean for " + child.getClass().getSimpleName() + " " + child.getName(), e); } + createAdditionalMBeans(child); } } public void childRemoved(ConfiguredObject object, ConfiguredObject child) { - synchronized (_children) + synchronized (_childrenLock) { AMQManagedObject mbean = _children.remove(child); if(mbean != null) @@ -196,6 +232,26 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual LOGGER.error("Exception while unregistering mbean for " + child.getClass().getSimpleName() + " " + child.getName(), e); } } + unregisterAdditionalMBeansIfPresent(child); + } + } + + private void unregisterAdditionalMBeansIfPresent(ConfiguredObject child) + { + Collection<ManagedObject> additionalMBeans = _additionalChildren.remove(child); + if (additionalMBeans != null) + { + for (ManagedObject mbean : additionalMBeans) + { + try + { + mbean.unregister(); + } + catch(Exception e) + { + LOGGER.error("Exception while unregistering mbean for " + child.getClass().getSimpleName() + " " + child.getName(), e); + } + } } } @@ -213,7 +269,7 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual public Collection<QueueMBean> getQueues() { Collection<AMQManagedObject> children; - synchronized (_children) + synchronized (_childrenLock) { children = new ArrayList<AMQManagedObject>(_children.values()); } @@ -235,7 +291,7 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual { _virtualHost.removeChangeListener(this); - synchronized (_children) + synchronized (_childrenLock) { for (AMQManagedObject mbean : _children.values()) { @@ -252,6 +308,11 @@ public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtual } } _children.clear(); + + for (ConfiguredObject child : new ArrayList<ConfiguredObject>(_additionalChildren.keySet())) + { + unregisterAdditionalMBeansIfPresent(child); + } } _managerMBean.unregister(); } diff --git a/qpid/java/systests/etc/config-systests.json b/qpid/java/systests/etc/config-systests.json index 12a8a5c5a6..55b73a4263 100644 --- a/qpid/java/systests/etc/config-systests.json +++ b/qpid/java/systests/etc/config-systests.json @@ -22,7 +22,7 @@ "name": "Broker", "defaultVirtualHost" : "test", "storeVersion": 1, - "modelVersion": "1.0", + "modelVersion": "1.3", "authenticationproviders" : [ { "name" : "plain", "type" : "PlainPasswordFile", diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/MessageStoreTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/MessageStoreTest.java index de36c6e413..2b21f4aeaa 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/MessageStoreTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/MessageStoreTest.java @@ -28,6 +28,7 @@ import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.log4j.Logger; import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; import org.apache.qpid.common.AMQPFilterTypes; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.framing.BasicContentHeaderProperties; @@ -604,7 +605,7 @@ public class MessageStoreTest extends QpidTestCase } } - private void sendMessageOnExchange(Exchange exchange, String routingKey, boolean deliveryMode) + private void sendMessageOnExchange(Exchange exchange, String routingKey, boolean deliveryMode) throws AMQStoreException { //Set MessagePersistence BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/QuotaMessageStore.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/QuotaMessageStore.java index 75ce0e68d8..7b43cd5022 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/QuotaMessageStore.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/QuotaMessageStore.java @@ -49,7 +49,6 @@ public class @Override public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) - throws Exception { Object overfullAttr = virtualHost.getAttribute(MessageStoreConstants.OVERFULL_SIZE_ATTRIBUTE); _persistentSizeHighThreshold = overfullAttr == null @@ -76,13 +75,13 @@ public class @Override public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler recoveryHandler, - TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception + TransactionLogRecoveryHandler tlogRecoveryHandler) { _stateManager.attainState(State.INITIALISED); } @Override - public void activate() throws Exception + public void activate() { _stateManager.attainState(State.ACTIVATING); _stateManager.attainState(State.ACTIVE); @@ -152,7 +151,7 @@ public class } @Override - public void close() throws Exception + public void close() { if (_closed.compareAndSet(false, true)) { diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java index cc0205085b..9cacffa086 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java @@ -48,7 +48,7 @@ public class SlowMessageStore implements MessageStore, DurableConfigurationStore // ***** MessageStore Interface. - public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws Exception + public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws AMQStoreException { _logger.info("Starting SlowMessageStore on Virtualhost:" + virtualHost.getName()); @@ -67,9 +67,17 @@ public class SlowMessageStore implements MessageStore, DurableConfigurationStore if (messageStoreClass != null) { - Class<?> clazz = Class.forName(messageStoreClass); - - Object o = clazz.newInstance(); + Class<?> clazz = null; + Object o = null; + try + { + clazz = Class.forName(messageStoreClass); + o = clazz.newInstance(); + } + catch(Exception e) + { + throw new AMQStoreException("Cannot instantiate message store:" + messageStoreClass, e ); + } if (!(o instanceof MessageStore)) { @@ -152,19 +160,19 @@ public class SlowMessageStore implements MessageStore, DurableConfigurationStore public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler messageRecoveryHandler, - TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception + TransactionLogRecoveryHandler tlogRecoveryHandler) throws AMQStoreException { _realStore.configureMessageStore(virtualHost, messageRecoveryHandler, tlogRecoveryHandler); } - public void close() throws Exception + public void close() throws AMQStoreException { doPreDelay("close"); _realStore.close(); doPostDelay("close"); } - public <M extends StorableMessageMetaData> StoredMessage<M> addMessage(M metaData) + public <M extends StorableMessageMetaData> StoredMessage<M> addMessage(M metaData) throws AMQStoreException { return _realStore.addMessage(metaData); } @@ -219,7 +227,7 @@ public class SlowMessageStore implements MessageStore, DurableConfigurationStore doPostDelay("update"); } - public Transaction newTransaction() + public Transaction newTransaction() throws AMQStoreException { doPreDelay("beginTran"); Transaction txn = new SlowTransaction(_realStore.newTransaction()); @@ -311,7 +319,7 @@ public class SlowMessageStore implements MessageStore, DurableConfigurationStore } @Override - public void activate() throws Exception + public void activate() throws AMQStoreException { _realStore.activate(); } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java index f396c79351..0f80345e57 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java @@ -195,7 +195,7 @@ public class ManagementLoggingTest extends AbstractTestLogging validateMessageID("MNG-1002", log); //Check the RMI Registry port is as expected - int mPort = getManagementPort(getPort()); + int mPort = getJmxManagementPort(getPort()); assertTrue("RMI Registry port not as expected(" + mPort + ").:" + getMessageString(log), getMessageString(log).endsWith(String.valueOf(mPort))); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AccessControlProviderRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AccessControlProviderRestTest.java index 312443cfa7..32c7f4daa7 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AccessControlProviderRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AccessControlProviderRestTest.java @@ -233,7 +233,7 @@ public class AccessControlProviderRestTest extends QpidRestTestCase assertCanAccessManagementInterface(accessControlProviderName2, true); } - public void testRemovalOfAccessControlProviderInErrorStateUsingManagementMode() throws Exception + public void testRemovalOfAccessControlProviderInQuiescedStateUsingManagementMode() throws Exception { stopBroker(); @@ -245,14 +245,14 @@ public class AccessControlProviderRestTest extends QpidRestTestCase assertFalse("ACL file should not exist", file.exists()); UUID id = getBrokerConfiguration().addAclFileConfiguration(file.getAbsolutePath()); getBrokerConfiguration().setSaved(false); - startBroker(0, true); + startBroker(0, true); getRestTestHelper().setUsernameAndPassword(BrokerOptions.MANAGEMENT_MODE_USER_NAME, MANAGEMENT_MODE_PASSWORD); Map<String, Object> acl = getRestTestHelper().getJsonAsSingletonList("/rest/accesscontrolprovider/" + TestBrokerConfiguration.ENTRY_NAME_ACL_FILE); assertEquals("Unexpected id", id.toString(), acl.get(AccessControlProvider.ID)); assertEquals("Unexpected path", file.getAbsolutePath() , acl.get(FileAccessControlProviderConstants.PATH)); - assertEquals("Unexpected state", State.ERRORED.name() , acl.get(AccessControlProvider.STATE)); + assertEquals("Unexpected state", State.QUIESCED.name() , acl.get(AccessControlProvider.STATE)); int status = getRestTestHelper().submitRequest("/rest/accesscontrolprovider/" + TestBrokerConfiguration.ENTRY_NAME_ACL_FILE, "DELETE", null); assertEquals("ACL was not deleted", 200, status); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java index 9028130c18..5dc1717751 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java @@ -52,7 +52,8 @@ public class Asserts assertNotNull("Virtualhost " + virtualHostName + " data are not found", virtualHost); assertAttributesPresent(virtualHost, VirtualHost.AVAILABLE_ATTRIBUTES, VirtualHost.TIME_TO_LIVE, VirtualHost.CREATED, VirtualHost.UPDATED, VirtualHost.SUPPORTED_QUEUE_TYPES, VirtualHost.STORE_PATH, - VirtualHost.CONFIG_PATH, VirtualHost.TYPE, VirtualHost.CONFIG_STORE_PATH, VirtualHost.CONFIG_STORE_TYPE); + VirtualHost.CONFIG_PATH, VirtualHost.TYPE, VirtualHost.CONFIG_STORE_PATH, VirtualHost.CONFIG_STORE_TYPE, + VirtualHost.QUIESCE_ON_MASTER_CHANGE); assertEquals("Unexpected value of attribute " + VirtualHost.NAME, virtualHostName, virtualHost.get(VirtualHost.NAME)); assertNotNull("Unexpected value of attribute " + VirtualHost.ID, virtualHost.get(VirtualHost.ID)); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java index 810b70a2ba..5700c3b8f5 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java @@ -510,4 +510,11 @@ public class RestTestHelper _useSslAuth = useSslAuth; _useSsl = true; } + + public static RestTestHelper createRestTestHelperWithDefaultCredentials(int port) + { + RestTestHelper helper = new RestTestHelper(port); + helper.setUsernameAndPassword("webadmin", "webadmin"); + return helper; + } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/VirtualHostRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/VirtualHostRestTest.java index 96ca0c2def..4a8b321997 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/VirtualHostRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/VirtualHostRestTest.java @@ -23,10 +23,17 @@ package org.apache.qpid.systest.rest; import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.IllegalStateException; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; import javax.jms.Session; import javax.servlet.http.HttpServletResponse; @@ -35,6 +42,7 @@ import org.apache.commons.configuration.XMLConfiguration; import org.apache.qpid.client.AMQConnection; import org.apache.qpid.server.model.Exchange; import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.queue.AMQQueueFactory; import org.apache.qpid.server.virtualhost.StandardVirtualHostFactory; @@ -494,6 +502,149 @@ public class VirtualHostRestTest extends QpidRestTestCase Asserts.assertQueue(queueName, "standard", queue, null); } + public void testConnectionToVirtualHostInQuiescedState() throws Exception + { + Connection connection = getConnection(); + assertProducingConsuming(connection); + + Map<String, Object> attributes = Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.QUIESCED.name()); + int status = getRestTestHelper().submitRequest("/rest/virtualhost/test", "PUT", attributes); + assertEquals("Unexpected http status", 200, status); + + Map<String, Object> hostAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/virtualhost/test"); + assertEquals("Unexpected state", State.QUIESCED.name(), hostAttributes.get(VirtualHost.STATE)); + + assertProducingConsuming(connection); + + try + { + getConnection(); + fail("A new connection to the QUIESCED virtual host should fail"); + } + catch(JMSException e) + { + // pass + } + + // test that operations to create exchange and queue are working for existing connection + Session session = connection.createSession(true, Session.SESSION_TRANSACTED); + String queueName = getTestQueueName() + 1; + String exchangeName = getTestName(); + Destination destination = session.createQueue("direct://" +exchangeName + "//" + queueName); + MessageConsumer consumer = session.createConsumer(destination); + sendMessage(session, destination, 1); + connection.start(); + Message m1 = consumer.receive(RECEIVE_TIMEOUT); + assertNotNull("Message 1 is not received", m1); + assertEquals("Unexpected first message received", 0, m1.getIntProperty(INDEX)); + session.commit(); + + Map<String, Object> queueAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/queue/test/" + queueName); + assertEquals("Unexpected queue name", queueName, queueAttributes.get(Queue.NAME)); + + Map<String, Object> exchangeAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/exchange/test/" + exchangeName); + assertEquals("Unexpected exchange name", exchangeName, exchangeAttributes.get(VirtualHost.NAME)); + + attributes = Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.ACTIVE.name()); + status = getRestTestHelper().submitRequest("/rest/virtualhost/test", "PUT", attributes); + assertEquals("Unexpected http status", 200, status); + + Connection connection2 = getConnection(); + assertProducingConsuming(connection2); + } + + public void testConnectionToVirtualHostInStoppedState() throws Exception + { + Connection connection = getConnection(); + assertProducingConsuming(connection); + + Map<String, Object> attributes = Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.STOPPED.name()); + int status = getRestTestHelper().submitRequest("/rest/virtualhost/test", "PUT", attributes); + assertEquals("Unexpected http status", 200, status); + + Map<String, Object> hostAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/virtualhost/test"); + assertEquals("Unexpected state", State.STOPPED.name(), hostAttributes.get(VirtualHost.STATE)); + + try + { + connection.createSession(true, Session.SESSION_TRANSACTED); + fail("Connection should be closed"); + } + catch(IllegalStateException e) + { + // pass + } + + try + { + getConnection(); + fail("A new connection to the STOPPED virtual host should fail"); + } + catch(JMSException e) + { + // pass + } + + attributes = Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.ACTIVE.name()); + status = getRestTestHelper().submitRequest("/rest/virtualhost/test", "PUT", attributes); + assertEquals("Unexpected http status", 200, status); + + Connection connection2 = getConnection(); + assertProducingConsuming(connection2); + } + + public void testRestartStoppedVirtualHost() throws Exception + { + Map<String, Object> attributes = Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.STOPPED.name()); + int status = getRestTestHelper().submitRequest("/rest/virtualhost/test", "PUT", attributes); + assertEquals("Unexpected http status", 200, status); + + Map<String, Object> hostAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/virtualhost/test"); + assertEquals("Unexpected state", State.STOPPED.name(), hostAttributes.get(VirtualHost.STATE)); + + restartBroker(); + + hostAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/virtualhost/test"); + assertEquals("Unexpected state after restart", State.STOPPED.name(), hostAttributes.get(VirtualHost.STATE)); + + status = getRestTestHelper().submitRequest("/rest/virtualhost/test", "PUT", Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.ACTIVE.name())); + assertEquals("Unexpected http status on state change to ACTIVE", 200, status); + + Connection connection = getConnection(); + assertProducingConsuming(connection); + } + + public void testRestartQuiescedVirtualHost() throws Exception + { + Map<String, Object> attributes = Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.QUIESCED.name()); + int status = getRestTestHelper().submitRequest("/rest/virtualhost/test", "PUT", attributes); + assertEquals("Unexpected http status", 200, status); + + Map<String, Object> hostAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/virtualhost/test"); + assertEquals("Unexpected state", State.QUIESCED.name(), hostAttributes.get(VirtualHost.STATE)); + + restartBroker(); + + hostAttributes = getRestTestHelper().getJsonAsSingletonList("/rest/virtualhost/test"); + assertEquals("Unexpected state after restart", State.QUIESCED.name(), hostAttributes.get(VirtualHost.STATE)); + + status = getRestTestHelper().submitRequest("/rest/virtualhost/test", "PUT", Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.ACTIVE.name())); + assertEquals("Unexpected http status on state change to ACTIVE", 200, status); + + Connection connection = getConnection(); + assertProducingConsuming(connection); + } + + public void testDeleteVirtualHost() throws Exception + { + Map<String, Object> attributes = Collections.<String, Object>singletonMap(VirtualHost.DESIRED_STATE, State.DELETED.name()); + int status = getRestTestHelper().submitRequest("/rest/virtualhost/" + TEST2_VIRTUALHOST, "PUT", attributes); + assertEquals("Unexpected http status", 200, status); + + List<Map<String, Object>> hostAttributes = getRestTestHelper().getJsonAsList("/rest/virtualhost/" + TEST2_VIRTUALHOST); + assertTrue("Virtual host should be deleted", hostAttributes.isEmpty()); + } + private void createExchange(String exchangeName, String exchangeType) throws IOException { HttpURLConnection connection = getRestTestHelper().openManagementConnection("/rest/exchange/test/" + exchangeName, "PUT"); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java index cc8bfb9433..3b5aef2e51 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java @@ -164,7 +164,7 @@ public class DurableSubscriptionTest extends QpidBrokerTestCase { //Verify that the queue was deleted by querying for its JMX MBean _jmxc = JMXConnnectionFactory.getJMXConnection(5000, "127.0.0.1", - getManagementPort(getPort()), USER, PASSWORD); + getJmxManagementPort(getPort()), USER, PASSWORD); _jmxConnected = true; _mbsc = _jmxc.getMBeanServerConnection(); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java index 4e5c4ca034..dcb0eb150b 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java @@ -82,7 +82,7 @@ public class JMXTestUtils public void open(final int brokerPort) throws Exception { int actualBrokerPort = _test.getPort(brokerPort); - int managementPort = _test.getManagementPort(actualBrokerPort); + int managementPort = _test.getJmxManagementPort(actualBrokerPort); _jmxc = JMXConnnectionFactory.getJMXConnection(5000, "127.0.0.1", managementPort, _user, _password); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java index 91dcf48001..d226c0785a 100755 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java @@ -159,6 +159,7 @@ public class QpidBrokerTestCase extends QpidTestCase public static final int DEFAULT_PORT = Integer.getInteger("test.port", DEFAULT_PORT_VALUE); public static final int FAILING_PORT = Integer.parseInt(System.getProperty("test.port.alt")); public static final int DEFAULT_MANAGEMENT_PORT = Integer.getInteger("test.mport", DEFAULT_JMXPORT_REGISTRYSERVER); + public static final int HTTP_MANAGEMENT_PORT = Integer.getInteger("test.hport", DEFAULT_HTTP_MANAGEMENT_PORT); public static final int DEFAULT_SSL_PORT = Integer.getInteger("test.port.ssl", DEFAULT_SSL_PORT_VALUE); protected String _brokerLanguage = System.getProperty(BROKER_LANGUAGE, JAVA); @@ -247,8 +248,9 @@ public class QpidBrokerTestCase extends QpidTestCase if (actualPort != DEFAULT_PORT) { configuration.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_AMQP_PORT, Port.PORT, actualPort); - configuration.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_RMI_PORT, Port.PORT, getManagementPort(actualPort)); - configuration.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_JMX_PORT, Port.PORT, getManagementPort(actualPort) + JMXPORT_CONNECTORSERVER_OFFSET); + configuration.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_RMI_PORT, Port.PORT, getJmxManagementPort(actualPort)); + configuration.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_JMX_PORT, Port.PORT, getJmxManagementPort(actualPort) + JMXPORT_CONNECTORSERVER_OFFSET); + configuration.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_HTTP_PORT, Port.PORT, getHttpManagementPort(actualPort)); } return configuration; } @@ -361,11 +363,16 @@ public class QpidBrokerTestCase extends QpidTestCase * * @return the management port that corresponds to the broker on the given port */ - protected int getManagementPort(int mainPort) + protected int getJmxManagementPort(int mainPort) { return mainPort + (DEFAULT_MANAGEMENT_PORT - DEFAULT_PORT); } + public int getHttpManagementPort(int mainPort) + { + return mainPort + (HTTP_MANAGEMENT_PORT - DEFAULT_PORT); + } + /** * The returned set of port numbers is only a guess because it assumes no ports have been overridden * using system properties. @@ -373,11 +380,13 @@ public class QpidBrokerTestCase extends QpidTestCase protected Set<Integer> guessAllPortsUsedByBroker(int mainPort) { Set<Integer> ports = new HashSet<Integer>(); - int managementPort = getManagementPort(mainPort); + int managementPort = getJmxManagementPort(mainPort); + int httpManagementPort = getHttpManagementPort(mainPort); int connectorServerPort = managementPort + JMXPORT_CONNECTORSERVER_OFFSET; ports.add(mainPort); ports.add(managementPort); + ports.add(httpManagementPort); ports.add(connectorServerPort); ports.add(DEFAULT_SSL_PORT); @@ -1488,4 +1497,19 @@ public class QpidBrokerTestCase extends QpidTestCase return supportedStoresClassToTypeMapping.get(storeClass); } + public void assertProducingConsuming(final Connection connection) throws Exception + { + Session session = connection.createSession(true, Session.SESSION_TRANSACTED); + Destination destination = session.createQueue(getTestQueueName()); + MessageConsumer consumer = session.createConsumer(destination); + sendMessage(session, destination, 1); + session.commit(); + connection.start(); + Message m1 = consumer.receive(RECEIVE_TIMEOUT); + assertNotNull("Message 1 is not received", m1); + assertEquals("Unexpected first message received", 0, m1.getIntProperty(INDEX)); + session.commit(); + session.close(); + } + } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestBrokerConfiguration.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestBrokerConfiguration.java index 9e893bb7bb..9ada95d728 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestBrokerConfiguration.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/TestBrokerConfiguration.java @@ -39,6 +39,7 @@ import org.apache.qpid.server.model.KeyStore; import org.apache.qpid.server.model.Plugin; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.ReplicationNode; import org.apache.qpid.server.model.TrustStore; import org.apache.qpid.server.model.UUIDGenerator; import org.apache.qpid.server.model.VirtualHost; @@ -115,10 +116,15 @@ public class TestBrokerConfiguration return null; } - public UUID addObjectConfiguration(String name, String type, Map<String, Object> attributes) + public UUID addObjectConfiguration(String type, Map<String, Object> attributes) + { + return addObjectConfiguration(type, attributes, Collections.<UUID>emptySet()); + } + + public UUID addObjectConfiguration(String type, Map<String, Object> attributes, Set<UUID> childrenIds) { UUID id = UUIDGenerator.generateRandomUUID(); - addObjectConfiguration(id, type, attributes); + addObjectConfiguration(id, type, attributes, childrenIds); return id; } @@ -127,7 +133,7 @@ public class TestBrokerConfiguration Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put(PluginFactory.PLUGIN_TYPE, MANAGEMENT_JMX_PLUGIN_TYPE); attributes.put(Plugin.NAME, ENTRY_NAME_JMX_MANAGEMENT); - return addObjectConfiguration(ENTRY_NAME_JMX_MANAGEMENT, Plugin.class.getSimpleName(), attributes); + return addObjectConfiguration(Plugin.class.getSimpleName(), attributes); } public UUID addHttpManagementConfiguration() @@ -135,7 +141,7 @@ public class TestBrokerConfiguration Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put(PluginFactory.PLUGIN_TYPE, MANAGEMENT_HTTP_PLUGIN_TYPE); attributes.put(Plugin.NAME, ENTRY_NAME_HTTP_MANAGEMENT); - return addObjectConfiguration(ENTRY_NAME_HTTP_MANAGEMENT, Plugin.class.getSimpleName(), attributes); + return addObjectConfiguration(Plugin.class.getSimpleName(), attributes); } public UUID addGroupFileConfiguration(String groupFilePath) @@ -160,44 +166,37 @@ public class TestBrokerConfiguration public UUID addPortConfiguration(Map<String, Object> attributes) { - String name = (String) attributes.get(Port.NAME); - return addObjectConfiguration(name, Port.class.getSimpleName(), attributes); + return addObjectConfiguration(Port.class.getSimpleName(), attributes); } public UUID addVirtualHostConfiguration(Map<String, Object> attributes) { - String name = (String) attributes.get(VirtualHost.NAME); - return addObjectConfiguration(name, VirtualHost.class.getSimpleName(), attributes); + return addObjectConfiguration(VirtualHost.class.getSimpleName(), attributes); } public UUID addAuthenticationProviderConfiguration(Map<String, Object> attributes) { - String name = (String) attributes.get(AuthenticationProvider.NAME); - return addObjectConfiguration(name, AuthenticationProvider.class.getSimpleName(), attributes); + return addObjectConfiguration(AuthenticationProvider.class.getSimpleName(), attributes); } public UUID addGroupProviderConfiguration(Map<String, Object> attributes) { - String name = (String) attributes.get(GroupProvider.NAME); - return addObjectConfiguration(name, GroupProvider.class.getSimpleName(), attributes); + return addObjectConfiguration(GroupProvider.class.getSimpleName(), attributes); } public UUID addAccessControlConfiguration(Map<String, Object> attributes) { - String name = (String) attributes.get(AccessControlProvider.NAME); - return addObjectConfiguration(name, AccessControlProvider.class.getSimpleName(), attributes); + return addObjectConfiguration(AccessControlProvider.class.getSimpleName(), attributes); } public UUID addTrustStoreConfiguration(Map<String, Object> attributes) { - String name = (String) attributes.get(TrustStore.NAME); - return addObjectConfiguration(name, TrustStore.class.getSimpleName(), attributes); + return addObjectConfiguration(TrustStore.class.getSimpleName(), attributes); } public UUID addKeyStoreConfiguration(Map<String, Object> attributes) { - String name = (String) attributes.get(KeyStore.NAME); - return addObjectConfiguration(name, KeyStore.class.getSimpleName(), attributes); + return addObjectConfiguration(KeyStore.class.getSimpleName(), attributes); } private boolean setObjectAttributes(ConfigurationEntry entry, Map<String, Object> attributes) @@ -240,20 +239,26 @@ public class TestBrokerConfiguration return null; } - private void addObjectConfiguration(UUID id, String type, Map<String, Object> attributes) + private void addObjectConfiguration(UUID id, String type, Map<String, Object> attributes, Set<UUID> childrenId) { - ConfigurationEntry entry = new ConfigurationEntry(id, type, attributes, Collections.<UUID> emptySet(), _store); - ConfigurationEntry root = _store.getRootEntry(); + ConfigurationEntry parent = _store.getRootEntry(); + addObjectConfigurationToParent(parent, id, type, attributes, childrenId); + } - Map<String, Collection<ConfigurationEntry>> children = root.getChildren(); + private void addObjectConfigurationToParent(ConfigurationEntry parent, UUID id, String type, Map<String, Object> attributes, + Set<UUID> childrenId) + { + ConfigurationEntry entry = new ConfigurationEntry(id, type, attributes, childrenId, _store); + + Map<String, Collection<ConfigurationEntry>> children = parent.getChildren(); verifyChildWithNameDoesNotExist(id, type, attributes, children); - Set<UUID> childrenIds = new HashSet<UUID>(root.getChildrenIds()); + Set<UUID> childrenIds = new HashSet<UUID>(parent.getChildrenIds()); childrenIds.add(id); - ConfigurationEntry newRoot = new ConfigurationEntry(root.getId(), root.getType(), root.getAttributes(), childrenIds, + ConfigurationEntry newParent = new ConfigurationEntry(parent.getId(), parent.getType(), parent.getAttributes(), childrenIds, _store); - _store.save(newRoot, entry); + _store.save(newParent, entry); } private void verifyChildWithNameDoesNotExist(UUID id, String type, @@ -308,4 +313,12 @@ public class TestBrokerConfiguration _store.save(newAp, pp); } + public UUID addReplicationNodeConfiguration(UUID hostId, Map<String, Object> replicationNodeAttributes) + { + ConfigurationEntry parent = _store.getEntry(hostId); + UUID id = UUID.randomUUID(); + addObjectConfigurationToParent(parent, id, ReplicationNode.class.getSimpleName(), replicationNodeAttributes, Collections.<UUID>emptySet()); + return id; + } + } |