/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.test.clustering.extended.ejb2.stateless; import static org.jboss.as.test.clustering.ClusteringTestConstants.CONTAINER_1; import static org.jboss.as.test.clustering.ClusteringTestConstants.CONTAINER_2; import static org.jboss.as.test.clustering.ClusteringTestConstants.DEPLOYMENT_1; import static org.jboss.as.test.clustering.ClusteringTestConstants.DEPLOYMENT_2; import static org.jboss.as.test.clustering.ClusteringTestConstants.NODES; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.naming.NamingException; import org.jboss.arquillian.container.test.api.ContainerController; import org.jboss.arquillian.container.test.api.Deployer; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.RunAsClient; import org.jboss.arquillian.container.test.api.TargetsContainer; import org.jboss.arquillian.junit.Arquillian; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.as.test.clustering.EJBClientContextSelector; import org.jboss.as.test.clustering.NodeNameGetter; import org.jboss.as.test.clustering.ejb.EJBDirectory; import org.jboss.as.test.clustering.ejb.RemoteEJBDirectory; import org.jboss.as.test.shared.TestSuiteEnvironment; import org.jboss.logging.Logger; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * EJB2 stateless bean - basic cluster tests - failover and load balancing. * * @author Paul Ferraro * @author Ondrej Chaloupka * @see org.jboss.as.test.clustering.cluster.ejb3.stateless.RemoteStatelessFailoverTestCase */ @RunWith(Arquillian.class) @RunAsClient public class RemoteStatelessFailoverTestCase { private static final Logger log = Logger.getLogger(RemoteStatelessFailoverTestCase.class); private static final String CLIENT_PROPERTIES = "cluster/ejb3/stateless/jboss-ejb-client.properties"; private static EJBDirectory directoryAnnotation; private static EJBDirectory directoryDD; private static final String ARCHIVE_NAME = "stateless-ejb2-failover-test"; private static final String ARCHIVE_NAME_DD = "stateless-ejb2-failover-dd-test"; private static final Integer PORT_2 = 8180; private static final String HOST_2 = TestSuiteEnvironment.getServerAddressNode1(); private static final String REMOTE_PORT_PROPERTY_NAME = "remote.connection.default.port"; private static final String REMOTE_HOST_PROPERTY_NAME = "remote.connection.default.host"; private static final String DEPLOYMENT_1_DD = DEPLOYMENT_1 + "-descriptor"; private static final String DEPLOYMENT_2_DD = DEPLOYMENT_2 + "-descriptor"; private static final Map<String, Boolean> deployed = new HashMap<String, Boolean>(); private static final Map<String, Boolean> started = new HashMap<String, Boolean>(); private static final Map<String, List<String>> container2deployment = new HashMap<String, List<String>>(); @BeforeClass public static void init() throws NamingException { directoryAnnotation = new RemoteEJBDirectory(ARCHIVE_NAME); directoryDD = new RemoteEJBDirectory(ARCHIVE_NAME_DD); deployed.put(DEPLOYMENT_1, false); deployed.put(DEPLOYMENT_2, false); deployed.put(DEPLOYMENT_1_DD, false); deployed.put(DEPLOYMENT_2_DD, false); started.put(CONTAINER_1, false); started.put(CONTAINER_2, false); List<String> deployments1 = new ArrayList<String>(); deployments1.add(DEPLOYMENT_1); deployments1.add(DEPLOYMENT_1_DD); container2deployment.put(CONTAINER_1, deployments1); List<String> deployments2 = new ArrayList<String>(); deployments2.add(DEPLOYMENT_2); deployments2.add(DEPLOYMENT_2_DD); container2deployment.put(CONTAINER_2, deployments2); } @AfterClass public static void destroy() throws NamingException { directoryAnnotation.close(); directoryDD.close(); } @ArquillianResource private ContainerController container; @ArquillianResource private Deployer deployer; @Deployment(name = DEPLOYMENT_1, managed = false, testable = false) @TargetsContainer(CONTAINER_1) public static Archive<?> createDeploymentForContainer1() { return createDeployment(); } @Deployment(name = DEPLOYMENT_2, managed = false, testable = false) @TargetsContainer(CONTAINER_2) public static Archive<?> createDeploymentForContainer2() { return createDeployment(); } @Deployment(name = DEPLOYMENT_1_DD, managed = false, testable = false) @TargetsContainer(CONTAINER_1) public static Archive<?> createDeploymentOnDescriptorForContainer1() { return createDeploymentOnDescriptor(); } @Deployment(name = DEPLOYMENT_2_DD, managed = false, testable = false) @TargetsContainer(CONTAINER_2) public static Archive<?> createDeploymentOnDescriptorForContainer2() { return createDeploymentOnDescriptor(); } private static Archive<?> createDeployment() { final JavaArchive jar = ShrinkWrap.create(JavaArchive.class, ARCHIVE_NAME + ".jar"); jar.addClasses(StatelessBeanBase.class, StatelessBean.class, StatelessRemote.class, StatelessRemoteHome.class); jar.addClass(NodeNameGetter.class); return jar; } private static Archive<?> createDeploymentOnDescriptor() { final JavaArchive jar = ShrinkWrap.create(JavaArchive.class, ARCHIVE_NAME_DD + ".jar"); jar.addClasses(StatelessBeanBase.class, StatelessBeanDD.class, StatelessRemote.class, StatelessRemoteHome.class); jar.addClass(NodeNameGetter.class); jar.addAsManifestResource(RemoteStatelessFailoverTestCase.class.getPackage(), "ejb-jar.xml", "ejb-jar.xml"); return jar; } @Test public void testFailoverOnStopAnnotatedBean() throws Exception { doFailover(true, directoryAnnotation, DEPLOYMENT_1, DEPLOYMENT_2); } @Test public void testFailoverOnStopBeanSpecifiedByDescriptor() throws Exception { doFailover(true, directoryDD, DEPLOYMENT_1_DD, DEPLOYMENT_2_DD); } @Test public void testFailoverOnUndeployAnnotatedBean() throws Exception { doFailover(false, directoryAnnotation, DEPLOYMENT_1, DEPLOYMENT_2); } @Test public void testFailoverOnUndeploySpecifiedByDescriptor() throws Exception { doFailover(false, directoryDD, DEPLOYMENT_1_DD, DEPLOYMENT_2_DD); } private void doFailover(boolean isStop, EJBDirectory directory, String deployment1, String deployment2) throws Exception { this.start(CONTAINER_1); this.deploy(CONTAINER_1, deployment1); // TODO Elytron: Once support for legacy EJB properties has been added back, actually set the EJB properties // that should be used for this test using CLIENT_PROPERTIES and ensure the EJB client context is reset // to its original state at the end of the test EJBClientContextSelector.setup(CLIENT_PROPERTIES); try { StatelessRemoteHome home = directory.lookupHome(StatelessBean.class, StatelessRemoteHome.class); StatelessRemote bean = home.create(); assertEquals("The only " + NODES[0] + " is active. Bean had to be invoked on it but it wasn't.", NODES[0], bean.getNodeName()); this.start(CONTAINER_2); this.deploy(CONTAINER_2, deployment2); if (isStop) { this.stop(CONTAINER_1); } else { this.undeploy(CONTAINER_1, deployment1); } assertEquals("Only " + NODES[1] + " is active. The bean had to be invoked on it but it wasn't.", NODES[1], bean.getNodeName()); } finally { // need to have the container started to undeploy deployment afterwards this.start(CONTAINER_1); // shutdown the containers undeployAll(); shutdownAll(); } } @Test public void testLoadbalanceAnnotatedBean() throws Exception { loadbalance(directoryAnnotation, DEPLOYMENT_1, DEPLOYMENT_2); } @Test public void testLoadbalanceSpecifiedByDescriptor() throws Exception { loadbalance(directoryDD, DEPLOYMENT_1_DD, DEPLOYMENT_2_DD); } /** * Basic load balance testing. A random distribution is used amongst nodes for client now. */ private void loadbalance(EJBDirectory directory, String deployment1, String deployment2) throws Exception { this.start(CONTAINER_1); this.deploy(CONTAINER_1, deployment1); this.start(CONTAINER_2); this.deploy(CONTAINER_2, deployment2); // TODO Elytron: Once support for legacy EJB properties has been added back, actually set the EJB properties // that should be used for this test using CLIENT_PROPERTIES and ensure the EJB client context is reset // to its original state at the end of the test EJBClientContextSelector.setup(CLIENT_PROPERTIES); int numberOfServers = 2; int numberOfCalls = 40; // there will be at least 20% of calls processed by all servers double serversProccessedAtLeast = 0.2; try { StatelessRemoteHome home = directory.lookupHome(StatelessBean.class, StatelessRemoteHome.class); StatelessRemote bean = home.create(); String node = bean.getNodeName(); log.trace("Node called : " + node); validateBalancing(bean, numberOfCalls, numberOfServers, serversProccessedAtLeast); Properties contextChangeProperties = new Properties(); contextChangeProperties.put(REMOTE_PORT_PROPERTY_NAME, PORT_2.toString()); contextChangeProperties.put(REMOTE_HOST_PROPERTY_NAME, HOST_2.toString()); EJBClientContextSelector.setup(CLIENT_PROPERTIES, contextChangeProperties); bean = home.create(); node = bean.getNodeName(); log.trace("Node called : " + node); validateBalancing(bean, numberOfCalls, numberOfServers, serversProccessedAtLeast); } finally { // undeploy&shutdown the containers undeployAll(); shutdownAll(); } } /** * Method calls the bean function getNodeName() {numCalls} times and checks whether all servers processed at least part of calls. * The necessary number of processed calls by each server is {minPercentage} of the number of all calls. */ private static void validateBalancing(StatelessRemote bean, int numCalls, int expectedServers, double minPercentage) { Map<String, Integer> callCount = new HashMap<String, Integer>(); int maxNumOfProcessedCalls = -1; int minNumOfProcessedCalls = Integer.MAX_VALUE; for (int i = 0; i < numCalls; i++) { String nodeName = bean.getNodeName(); Integer count = callCount.get(nodeName); count = count == null ? 1 : ++count; callCount.put(nodeName, count); } Assert.assertEquals("It was running " + expectedServers + " servers but not all of them were used for loadbalancing.", expectedServers, callCount.size()); for (Integer count : callCount.values()) { maxNumOfProcessedCalls = count > maxNumOfProcessedCalls ? count : maxNumOfProcessedCalls; minNumOfProcessedCalls = count < minNumOfProcessedCalls ? count : minNumOfProcessedCalls; } Assert.assertTrue("Minimal number of calls done to all servers have to be " + minPercentage * numCalls + " but was " + minNumOfProcessedCalls, minPercentage * numCalls <= minNumOfProcessedCalls); log.trace("All " + expectedServers + " servers processed at least " + minNumOfProcessedCalls + " of calls"); } private void undeployAll() { for (String container : container2deployment.keySet()) { for (String deployment : container2deployment.get(container)) { undeploy(container, deployment); } } } private void shutdownAll() { for (String container : container2deployment.keySet()) { stop(container); } } private void deploy(String container, String deployment) { if (started.get(container) && !deployed.get(deployment)) { this.deployer.deploy(deployment); deployed.put(deployment, true); } } private void undeploy(String container, String deployment) { if (started.get(container) && deployed.get(deployment)) { this.deployer.undeploy(deployment); deployed.put(deployment, false); } } private void start(String container) { if (!started.get(container)) { this.container.start(container); started.put(container, true); } } private void stop(String container) { if (started.get(container)) { this.container.stop(container); started.put(container, false); } } }