diff --git a/ejb3/src/main/java/org/jboss/as/ejb3/remote/AssociationImpl.java b/ejb3/src/main/java/org/jboss/as/ejb3/remote/AssociationImpl.java index 2a6e498c6c3c..709706fdf38c 100644 --- a/ejb3/src/main/java/org/jboss/as/ejb3/remote/AssociationImpl.java +++ b/ejb3/src/main/java/org/jboss/as/ejb3/remote/AssociationImpl.java @@ -46,6 +46,7 @@ import org.jboss.ejb.client.EJBLocator; import org.jboss.ejb.client.EJBMethodLocator; import org.jboss.ejb.client.EJBModuleIdentifier; +import org.jboss.ejb.client.NodeAffinity; import org.jboss.ejb.client.SessionID; import org.jboss.ejb.client.StatefulEJBLocator; import org.jboss.ejb.server.Association; @@ -247,19 +248,22 @@ public SecurityIdentity getSecurityIdentity() { private void updateAffinities(InvocationRequest invocationRequest, Map attachments, EJBLocator ejbLocator, ComponentView componentView) { Affinity legacyAffinity = null; Affinity weakAffinity = null; - Affinity clusterAffinity = getClusterAffinity(); + Affinity strongAffinity = null; if (ejbLocator.isStateful() && componentView.getComponent() instanceof StatefulSessionComponent) { final StatefulSessionComponent statefulSessionComponent = (StatefulSessionComponent) componentView.getComponent(); + strongAffinity = getStrongAffinity(statefulSessionComponent); weakAffinity = legacyAffinity = getWeakAffinity(statefulSessionComponent, ejbLocator.asStateful()); } else if (componentView.getComponent() instanceof StatelessSessionComponent) { - // V3 and less used cluster affinity as a weak affinity for SLSBs - legacyAffinity = clusterAffinity; + // Stateless invocations no not require strong affinity, only weak affinity to nodes within the same cluster, if present. + // However, since V3, the EJB client does not support weak affinity updates referencing a cluster (and even then, only via Affinity.WEAK_AFFINITY_CONTEXT_KEY), only a node. + // Until this is corrected, we need to use the strong affinity instead. + strongAffinity = legacyAffinity = this.getStatelessAffinity(); } - // Always use the cluster as the strong affinity, if there is one - if (clusterAffinity != null) { - invocationRequest.updateStrongAffinity(clusterAffinity); + // cause the affinity values to get sent back to the client + if (strongAffinity != null && !(strongAffinity instanceof NodeAffinity)) { + invocationRequest.updateStrongAffinity(strongAffinity); } if (weakAffinity != null && !weakAffinity.equals(Affinity.NONE)) { @@ -269,6 +273,10 @@ private void updateAffinities(InvocationRequest invocationRequest, Map statefulEJBLocator) { final SessionID sessionID = statefulEJBLocator.getSessionId(); return getWeakAffinity(statefulSessionComponent, sessionID); @@ -597,7 +613,7 @@ private static Affinity getWeakAffinity(final StatefulSessionComponent statefulS return statefulSessionComponent.getCache().getWeakAffinity(sessionID); } - private Affinity getClusterAffinity() { + private Affinity getStatelessAffinity() { Registry> registry = this.clientMappingRegistry; Group group = registry != null ? registry.getGroup() : null; diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/PassivationDisabledRemoteStatefulEjbFailoverTestCase.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/PassivationDisabledRemoteStatefulEjbFailoverTestCase.java new file mode 100644 index 000000000000..1cd069597cbf --- /dev/null +++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/PassivationDisabledRemoteStatefulEjbFailoverTestCase.java @@ -0,0 +1,97 @@ +package org.jboss.as.test.clustering.cluster.ejb.remote; + +import java.util.PropertyPermission; +import javax.ejb.NoSuchEJBException; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.TargetsContainer; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.as.test.clustering.cluster.AbstractClusteringTestCase; +import org.jboss.as.test.clustering.cluster.ejb.remote.bean.Incrementor; +import org.jboss.as.test.clustering.cluster.ejb.remote.bean.IncrementorBean; +import org.jboss.as.test.clustering.cluster.ejb.remote.bean.PassivationDisabledStatefulIncrementorBean; +import org.jboss.as.test.clustering.cluster.ejb.remote.bean.Result; +import org.jboss.as.test.clustering.ejb.EJBDirectory; +import org.jboss.as.test.clustering.ejb.RemoteEJBDirectory; +import org.jboss.as.test.shared.TimeoutUtil; +import org.jboss.as.test.shared.integration.ejb.security.PermissionUtils; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.wildfly.common.function.ExceptionSupplier; + +/** + * Test the following properties of passivation-disabled SFSB when deployed in a cluster: + * - stickiness of passivation-disabled SFSB to the node its is created on, as well as + * - verify that it does not fail over to another node in the cluster when the node it is created on goes down + * . + * @author Paul Ferraro + */ +@RunWith(Arquillian.class) +public class PassivationDisabledRemoteStatefulEjbFailoverTestCase extends AbstractClusteringTestCase { + private static final int COUNT = 20; + private static final long CLIENT_TOPOLOGY_UPDATE_WAIT = TimeoutUtil.adjust(5000); + private static final String MODULE_NAME = PassivationDisabledRemoteStatefulEjbFailoverTestCase.class.getSimpleName(); + + @Deployment(name = DEPLOYMENT_1, managed = false, testable = false) + @TargetsContainer(NODE_1) + public static Archive createDeploymentForContainer1() { + return createDeployment(); + } + + @Deployment(name = DEPLOYMENT_2, managed = false, testable = false) + @TargetsContainer(NODE_2) + public static Archive createDeploymentForContainer2() { + return createDeployment(); + } + + private static Archive createDeployment() { + return ShrinkWrap.create(JavaArchive.class, MODULE_NAME + ".jar") + .addPackage(EJBDirectory.class.getPackage()) + .addClasses(Result.class, Incrementor.class, IncrementorBean.class, PassivationDisabledStatefulIncrementorBean.class) + .addAsManifestResource(PermissionUtils.createPermissionsXmlAsset(new PropertyPermission(NODE_NAME_PROPERTY, "read")), "permissions.xml") + ; + } + + private final ExceptionSupplier directoryProvider; + + public PassivationDisabledRemoteStatefulEjbFailoverTestCase() { + this.directoryProvider = () -> new RemoteEJBDirectory(MODULE_NAME); + } + + @Test + public void test() throws Exception { + try (EJBDirectory directory = this.directoryProvider.get()) { + Incrementor bean = directory.lookupStateful(PassivationDisabledStatefulIncrementorBean.class, Incrementor.class); + + Result result = bean.increment(); + String target = result.getNode(); + int count = 1; + + Assert.assertEquals(count++, result.getValue().intValue()); + + // Bean should retain strong affinity for this node + for (int i = 0; i < COUNT; ++i) { + result = bean.increment(); + Assert.assertEquals(count++, result.getValue().intValue()); + Assert.assertEquals(String.valueOf(i), target, result.getNode()); + } + + undeploy(this.findDeployment(target)); + + Thread.sleep(CLIENT_TOPOLOGY_UPDATE_WAIT); + + try { + result = bean.increment(); + + // Bean should fail to failover to other node + Assert.fail(result.getNode()); + } catch (NoSuchEJBException e) { + // Failover should fail + } + } + } +} diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/bean/PassivationDisabledStatefulIncrementorBean.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/bean/PassivationDisabledStatefulIncrementorBean.java new file mode 100644 index 000000000000..654d5264e106 --- /dev/null +++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/ejb/remote/bean/PassivationDisabledStatefulIncrementorBean.java @@ -0,0 +1,14 @@ +package org.jboss.as.test.clustering.cluster.ejb.remote.bean; + +import javax.ejb.Remote; +import javax.ejb.Stateful; + +/** + * SFSB with passivation disabled, behaves as a singleton bean even if deployed on a clustered node + * + * @author Paul Ferarro + */ +@Stateful(passivationCapable = false) +@Remote(Incrementor.class) +public class PassivationDisabledStatefulIncrementorBean extends IncrementorBean { +}