/* * JBoss, Home of Professional Open Source. * Copyright 2007, Red Hat Middleware LLC, 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.test.cluster.invokerha; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.transaction.Transaction; import javax.transaction.UserTransaction; import org.jboss.ha.framework.interfaces.LoadBalancePolicy; import org.jboss.ha.framework.interfaces.TransactionStickyFirstAvailable; import org.jboss.ha.framework.interfaces.TransactionStickyFirstAvailableIdenticalAllProxies; import org.jboss.ha.framework.interfaces.TransactionStickyRandomRobin; import org.jboss.ha.framework.interfaces.TransactionStickyRoundRobin; import org.jboss.invocation.Invocation; import org.jboss.invocation.Invoker; import org.jboss.invocation.InvokerProxyHA; import org.jboss.invocation.ServiceUnavailableException; import org.jboss.logging.Logger; import org.jboss.test.cluster.invokerha.InvokerHaTransactionalMockUtils.MockUserTransaction; import org.jboss.test.cluster.invokerha.InvokerHaTransactionalMockUtils.MockTransactionStickyInterceptor; /** * AbstractInvokerHaTransactionSticky. * * @author <a href="mailto:galder.zamarreno@jboss.com">Galder Zamarreno</a> */ public abstract class AbstractInvokerHaTransactionSticky extends AbstractInvokerHa { private static final Logger log = Logger.getLogger(AbstractInvokerHaTransactionSticky.class); private Set<Object> chosenTargets; @Override public void testTransactionalSuccessfulCallsRoundRobin() throws Exception { /* same proxies used in simulated transactions */ transactionalSuccessfulCalls(TransactionStickyRoundRobin.class, false); /* different proxies in simulated transactions */ transactionalSuccessfulCalls(TransactionStickyRoundRobin.class, true); } @Override public void testTransactionalSuccessfulCallsFirstAvailable() throws Exception { /* same proxies used in simulated transactions */ transactionalSuccessfulCalls(TransactionStickyFirstAvailable.class, false); /* different proxies in simulated transactions */ transactionalSuccessfulCalls(TransactionStickyFirstAvailable.class, true); } @Override public void testTransactionalSuccessfulCallsFirstAvailableIndenticalAllProxies() throws Exception { /* same proxies used in simulated transactions */ transactionalSuccessfulCalls(TransactionStickyFirstAvailableIdenticalAllProxies.class, false); /* different proxies in simulated transactions */ transactionalSuccessfulCalls(TransactionStickyFirstAvailableIdenticalAllProxies.class, true); } @Override public void testTransactionalSuccessfulCallsRandomRobin() throws Exception { /* same proxies used in simulated transactions */ transactionalSuccessfulCalls(TransactionStickyRandomRobin.class, false); /* different proxies in simulated transactions */ transactionalSuccessfulCalls(TransactionStickyRandomRobin.class, true); } public void testTransactionalCallsWithNodeShutdown() throws Exception { transactionalCallsWithNodeShutdown(TransactionStickyRandomRobin.class); } public void testSuccessfulCallsRoundRobin() throws Exception { /* test transaction sticky round robin lbp without transactions, should * behave like standard round robin */ successfulCalls(TransactionStickyRoundRobin.class); } public void testSuccessfulCallsFirstAvailable() throws Exception { /* test transaction sticky first available lbp without transactions, should * behave like standard first available */ successfulCalls(TransactionStickyFirstAvailable.class); } public void testSuccessfulCallsFirstAvailableIndenticalAllProxies() throws Exception { /* test transaction sticky first available identical all proxies lbp * without transactions, should behave like standard identical all * proxies */ successfulCalls(TransactionStickyFirstAvailableIdenticalAllProxies.class); } public void testSuccessfulCallsRandomRobin() throws Exception { /* test transaction sticky random robin lbp without transactions, should * behave like standard random robin */ successfulCalls(TransactionStickyRandomRobin.class); } public void testFailBeforeServer() throws Exception { /* if failure happens before reaching the server, regardless of whether * the failure happened in the 1st or the Nth invocation, it's not * recoverable */ transactionalFailureCalls(InvokerHaFailureType.BEFORE_SERVER, TransactionStickyRandomRobin.class, false); } public void testFailAfterServerNotCompletedButSuccessAfter() throws Exception { /* we use new proxies in between tests for 1st and Nth * call so that results from Nth call don't get polluted with what * happened in the 1st call test. */ transactionalFailureCalls(InvokerHaFailureType.AFTER_SERVER_NOT_COMPLETED_BUT_SUCCESS_AFTER, TransactionStickyRandomRobin.class, true); } public void testFailAfterServerNotCompletedBothServers() throws Exception { /* we use new proxies in between tests for 1st and Nth call * so that results from Nth call don't get polluted with what happened in * the 1st call test. */ transactionalFailureCalls(InvokerHaFailureType.AFTER_SERVER_NOT_COMPLETED_BOTH_SERVERS, TransactionStickyRandomRobin.class, true); } public void testFailAfterServerCompleted() throws Exception { /* We use new proxies in between tests for 1st and Nth call * so that results from Nth call don't get polluted with what happened in * the 1st call test. */ transactionalFailureCalls(InvokerHaFailureType.AFTER_SERVER_COMPLETED, TransactionStickyRandomRobin.class, true); } @Override protected void transactionalSuccessfulCalls(Class<? extends LoadBalancePolicy> policyClass, boolean newProxiesInBetweenTransactions) { log.debug("transactional successfull calls [policy=" + policyClass + ",newProxiesInBetweenTransactions=" + newProxiesInBetweenTransactions + "]"); try { createNewProxies(0, policyClass, true); UserTransaction ut = new MockUserTransaction(clientUserTransactionProxy, infrastructure); ut.begin(); performTransactionalStickyCalls(3, null, policyClass, newProxiesInBetweenTransactions); ut.commit(); /* either set would do because they should be the same */ Set<Object> chosenTargetsTx1 = chosenTargets;; if (newProxiesInBetweenTransactions) { createNewProxies(0, policyClass, false); } ut.begin(); performTransactionalStickyCalls(3, null, policyClass, newProxiesInBetweenTransactions); ut.commit(); /* either set would do because they should be the same */ Set<Object> chosenTargetsTx2 = chosenTargets; assertChosenTargetsInBetweenTx(policyClass, chosenTargetsTx1, chosenTargetsTx2, newProxiesInBetweenTransactions); } catch(Exception e) { /* catching to log the error properly (JUnit in eclipse does not show * correctly exceptions from invokers) and fail */ log.error("error", e); fail(); } } protected void transactionalCallsWithNodeShutdown(Class<? extends LoadBalancePolicy> policyClass) { log.debug("transactional calls with node shutdown [policy=" + policyClass + "]"); try { createNewProxies(0, policyClass, true); UserTransaction ut = new MockUserTransaction(clientUserTransactionProxy, infrastructure); ut.begin(); performTransactionalCallsWithNodeShutdown(null, policyClass); ut.commit(); } catch(Exception e) { /* catching to log the error properly (JUnit in eclipse does not show * correctly exceptions from invokers) and fail */ log.error("Error", e); fail(); } } protected void successfulCalls(Class<? extends LoadBalancePolicy> policyClass) { log.debug("successfull calls [policy=" + policyClass); try { createNewProxies(0, policyClass, true); performCalls(3, null, policyClass); } catch(Exception e) { /* catching to log the error properly (JUnit in eclipse does not show * correctly exceptions from invokers) and fail */ log.error("error", e); fail(); } } protected void performTransactionalStickyCalls(int numberPairCalls, Transaction tx, Class<? extends LoadBalancePolicy> policyClass, boolean newProxiesInBetweenTransactions) throws Exception { Invocation inv; Set<Object> chosenTargetsDateTimeTeller = new HashSet<Object>(1); Set<Object> chosenTargetsSystemTimeTeller = new HashSet<Object>(1); for (int i = 0; i < numberPairCalls; i++) { /* create invocation to date time teller */ inv = infrastructure.createDateTimeTellerInvocation(tx, null, timeTellerProxy); /* invoke on proxy passing the invocation */ log.debug(invokeTimeTellerProxy(inv)); /* assert post conditions after invocation */ chosenTargetsDateTimeTeller = assertSuccessfulPostConditions(inv, chosenTargetsDateTimeTeller); /* create invocation to system time teller */ inv = infrastructure.createSystemTimeTellerInvocation(tx, null, systemTimeProxy); /* invoke on proxy passing the invocation */ log.debug(invokeSystemTimeProxy(inv)); /* assert post conditions after invocation */ chosenTargetsSystemTimeTeller = assertSuccessfulPostConditions(inv, chosenTargetsSystemTimeTeller); } /* for the duration of a transaction, all chosen targets should be the same */ assertEquals(chosenTargetsDateTimeTeller, chosenTargetsSystemTimeTeller); chosenTargets = chosenTargetsDateTimeTeller; } protected Set<Object> assertSuccessfulPostConditions(Invocation inv, Set<Object> chosenTargets) { assertEquals(0, inv.getAsIsValue("FAILOVER_COUNTER")); Object chosenTarget = inv.getTransientValue(invokerHaFactory.getChosenTargetKey()); assertNotNull(chosenTarget); /* check tx failover authorisations */ assertTrue("transaction should have reached the server", invokerHaFactory.getTxFailoverAuthorizationsMap().containsKey(transactionalMockUtils.getTpcf().getUid())); /* add target to singleton set, there must only be one different target within a transaction */ chosenTargets.add(chosenTarget); assertEquals(1, chosenTargets.size()); return chosenTargets; } protected void performTransactionalCallsWithNodeShutdown(Transaction tx, Class<? extends LoadBalancePolicy> policyClass) throws Exception { Invocation inv; Set<Object> chosenTargetsDateTimeTeller = new HashSet<Object>(1); /* create invocation to date time teller */ inv = infrastructure.createDateTimeTellerInvocation(tx, null, timeTellerProxy); /* invoke on proxy passing the invocation */ log.debug(invokeTimeTellerProxy(inv)); /* assert post conditions after invocation */ chosenTargetsDateTimeTeller = assertSuccessfulPostConditions(inv, chosenTargetsDateTimeTeller); List<Object> newReplicants = undeployChosenTargetNode(chosenTargetsDateTimeTeller.toArray()[0]); ((InvokerProxyHA)timeTellerProxy).updateClusterInfo((ArrayList<Object>)newReplicants, 3); inv = infrastructure.createDateTimeTellerInvocation(tx, null, timeTellerProxy); try { log.debug(invokeTimeTellerProxy(inv)); fail("Should have thrown an exception because sticky target is no longer available"); } catch(Exception e) { log.debug("Expected exception", e); assertTrue(e.getCause() instanceof ServiceUnavailableException); } } /** * Undeploy chosen target from the cluster and return the list of remaining * targets in cluster. */ protected abstract List undeployChosenTargetNode(Object chosenTarget); protected void assertChosenTargetsInBetweenTx(Class<? extends LoadBalancePolicy> policyClass, Set<?> chosenTargetsTx1, Set<?> chosenTargetsTx2, boolean newProxiesInBetweenTransactions) { if (policyClass.equals(TransactionStickyRoundRobin.class)) { if (!newProxiesInBetweenTransactions) { /* If we're using sticky round robin and we didn't change proxies * in between transactions, we can guarantee that the chosen targets * were different between transactions.*/ assertNotSame(chosenTargetsTx1, chosenTargetsTx2); } } else if (policyClass.equals(TransactionStickyFirstAvailable.class)) { if (!newProxiesInBetweenTransactions) { /* If we're using sticky first available and we didn't change proxies * in between transactions, we can guarantee that the chosen targets * are equals between transactions.*/ assertEquals(chosenTargetsTx1, chosenTargetsTx2); } } else if (policyClass.equals(TransactionStickyFirstAvailableIdenticalAllProxies.class)) { /* If we're using sticky first available identical all proxies, * regardless of whether we're using same proxies or not, we can * guarantee that the chosen targets are the same between * transactions.*/ assertEquals(chosenTargetsTx1, chosenTargetsTx2); } } protected void transactionalFailureCalls(InvokerHaFailureType failureType, Class<? extends LoadBalancePolicy> policyClass, boolean newProxiesInBetweenFailures) throws Exception { createNewProxies(0, policyClass, true); try { /* fail in 1st call */ UserTransaction ut = new MockUserTransaction(clientUserTransactionProxy, infrastructure); ut.begin(); failureCall(failureType, true, policyClass, newProxiesInBetweenFailures); ut.commit(); if (newProxiesInBetweenFailures) { createNewProxies(0, policyClass, false); } /* fail in Nth call */ ut.begin(); failureCall(failureType, false, policyClass, newProxiesInBetweenFailures); ut.commit(); } catch(Exception e) { /* catching to log the error properly (JUnit in eclipse does not show * correctly exceptions from invokers) and fail */ log.error("error", e); fail(); } } protected void failureCall(InvokerHaFailureType failureType, boolean injectFailureIn1stCall, Class<? extends LoadBalancePolicy> policyClass, boolean newProxiesInBetweenFailures) throws Exception { Invocation inv; if (injectFailureIn1stCall) { /* When failure must happen in the very 1st call, we create a 1st * invocation with the failure injected */ inv = infrastructure.createDateTimeTellerInvocation(null, failureType, timeTellerProxy); } else { /* When failure is not in first call, we do a call to each bean before * injecting the faliure */ performTransactionalStickyCalls(1, null, policyClass, newProxiesInBetweenFailures); /* Now we create the invocation with the failure injected */ inv = infrastructure.createDateTimeTellerInvocation(null, failureType, timeTellerProxy); } try { log.debug(invokeTimeTellerProxy(inv)); if (failureType.isRecoverable(injectFailureIn1stCall)) { failureType.assertFailoverCounter(injectFailureIn1stCall, inv.getAsIsValue("FAILOVER_COUNTER")); inv = infrastructure.createDateTimeTellerInvocation(null, null, timeTellerProxy); log.debug(invokeTimeTellerProxy(inv)); } else { fail("should have failed, failover is not allowed for " + failureType); } } catch (Exception e) { log.debug("Exception (could be expected) during failure call testing", e); if (failureType.isRecoverable(injectFailureIn1stCall)) { fail("should have failed, failover is not allowed for " + failureType); } else { assertNull("transaction on the client side should be null", inv.getTransaction()); failureType.assertFailoverCounter(injectFailureIn1stCall, inv.getAsIsValue("FAILOVER_COUNTER")); failureType.assertException(e.getCause()); } } } protected Object invokeTimeTellerProxy(Invocation inv) throws Exception { return invokeProxy(timeTellerProxy, inv); } protected Object invokeSystemTimeProxy(Invocation inv) throws Exception { return invokeProxy(systemTimeProxy, inv); } protected Object invokeProxy(Invoker invoker, Invocation inv) throws Exception { try { return new MockTransactionStickyInterceptor(invoker).invoke(inv); } catch(Throwable t) { throw new Exception("Throwable", t); } } }