/*
* Hibernate OGM, Domain model persistence for NoSQL datastores
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.ogm.datastore.infinispanremote.utils;
import static org.jboss.as.controller.client.helpers.ClientConstants.CONTROLLER_PROCESS_STATE_STARTING;
import static org.jboss.as.controller.client.helpers.ClientConstants.CONTROLLER_PROCESS_STATE_STOPPING;
import static org.jboss.as.controller.client.helpers.ClientConstants.OP;
import static org.jboss.as.controller.client.helpers.ClientConstants.OP_ADDR;
import static org.jboss.as.controller.client.helpers.ClientConstants.OUTCOME;
import static org.jboss.as.controller.client.helpers.ClientConstants.READ_ATTRIBUTE_OPERATION;
import static org.jboss.as.controller.client.helpers.ClientConstants.RESULT;
import static org.jboss.as.controller.client.helpers.ClientConstants.SUCCESS;
import static org.jboss.as.controller.client.helpers.ClientConstants.NAME;
import static org.jboss.as.controller.client.helpers.ClientConstants.SUBSYSTEM;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.infinispan.client.hotrod.RemoteCache;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.dmr.ModelNode;
import org.wildfly.core.launcher.Launcher;
import org.wildfly.core.launcher.StandaloneCommandBuilder;
/**
* A JUnit rule which starts the Hot Rod server, and finally closes it.
*
* @author Sanne Grinovero
*/
public final class RemoteHotRodServerRule extends org.junit.rules.ExternalResource {
private static final int MAX_WAIT_MILLISECONDS = 120 * 1000;
private static final int STATE_REFRESH_MILLISECONDS = 50;
private static final int MAX_STATE_REFRESH_ATTEMPTS = MAX_WAIT_MILLISECONDS / STATE_REFRESH_MILLISECONDS;
/**
* An atomic static flag to make it possible to reuse this class both as a global JUnit listener and as a Rule, and
* avoid starting the server twice.
*/
private static final AtomicBoolean running = new AtomicBoolean();
private final int portOffset;
/**
* Reference to the Hot Rod Server process. Access protected by synchronization on the static field "running".
*/
private Process hotRodServer;
public RemoteHotRodServerRule() {
this.portOffset = 0;
}
/**
* @param portOffset Allows to specify a port offset, equivalent to: -Djboss.socket.binding.port-offset=X
* This is needed when running multiple instances, or to run it in parallel with WildFly for integration tests.
*/
public RemoteHotRodServerRule(int portOffset) {
this.portOffset = portOffset;
}
@Override
public void before() throws Exception {
// Synchronize on the static field to defend against concurrent launches,
// e.g. the usage as JUnit Rule concurrently with the usage as global test listener in Surefire.
synchronized ( running ) {
if ( running.compareAndSet( false, true ) ) {
String InfinispanVersion = RemoteCache.class.getPackage().getImplementationVersion();
StandaloneCommandBuilder builder = StandaloneCommandBuilder
.of( "target/node1/infinispan-server-" + InfinispanVersion );
builder
.setServerReadOnlyConfiguration( "wildfly-trimmed-config.xml" );
if ( portOffset != 0 ) {
builder.addJavaOption( "-Djboss.socket.binding.port-offset=" + portOffset );
}
Launcher launcher = Launcher.of( builder );
hotRodServer = launcher.inherit().launch();
waitForRunning();
}
}
}
@Override
public void after() {
synchronized ( running ) {
if ( hotRodServer != null ) {
running.set( false );
hotRodServer.destroyForcibly();
messageOut( "Server Killed" );
}
}
}
public void waitForRunning() throws Exception {
try ( ModelControllerClient client = ModelControllerClient.Factory.create( "localhost", 9990 + portOffset ) ) {
waitForServerBoot( client );
waitForCacheManagerBoot( client );
}
}
private void waitForServerBoot(ModelControllerClient client) {
for ( int attempts = 0; attempts < MAX_STATE_REFRESH_ATTEMPTS; attempts++ ) {
if ( isServerInRunningState( client ) ) {
messageOut( "Server is now running" );
return;
}
waitOrAbort();
}
timedOut();
}
private void waitForCacheManagerBoot(ModelControllerClient client) {
for ( int attempts = 0; attempts < MAX_STATE_REFRESH_ATTEMPTS; attempts++ ) {
if ( isCacheExists( client ) ) {
messageOut( "CacheManager is now running" );
return;
}
waitOrAbort();
}
timedOut();
}
private void waitOrAbort() {
try {
Thread.sleep( STATE_REFRESH_MILLISECONDS );
}
catch (InterruptedException e) {
throw new RuntimeException( "Interrupted while waiting for Hot Rod server to have booted successfully" );
}
}
private void timedOut() {
throw new RuntimeException( "Timed out while waiting for Hot Rod server to have booted successfully" );
}
private boolean isServerInRunningState(ModelControllerClient client) {
try {
ModelNode op = new ModelNode();
op.get( OP ).set( READ_ATTRIBUTE_OPERATION );
op.get( OP_ADDR ).setEmptyList();
op.get( NAME ).set( "server-state" );
ModelNode rsp = client.execute( op );
return SUCCESS.equals( rsp.get( OUTCOME ).asString() )
&& !CONTROLLER_PROCESS_STATE_STARTING.equals( rsp.get( RESULT ).asString() )
&& !CONTROLLER_PROCESS_STATE_STOPPING.equals( rsp.get( RESULT ).asString() );
}
catch (RuntimeException rte) {
throw rte;
}
catch (IOException ex) {
return false;
}
}
private boolean isCacheExists(ModelControllerClient client) {
try {
PathAddress pathAddress = PathAddress.pathAddress( SUBSYSTEM, "datagrid-infinispan" )
.append( "cache-container", "clustered" );
ModelNode op = new ModelNode();
op.get( OP ).set( READ_ATTRIBUTE_OPERATION );
op.get( OP_ADDR ).set( pathAddress.toModelNode() );
op.get( NAME ).set( "cache-manager-status" );
ModelNode resp = client.execute( op );
return SUCCESS.equals( resp.get( OUTCOME ).asString() ) && "RUNNING".equals( resp.get( RESULT ).asString() );
}
catch (RuntimeException rte) {
throw rte;
}
catch (IOException ex) {
return false;
}
}
public static void main(String[] args) throws Exception {
RemoteHotRodServerRule rule = new RemoteHotRodServerRule();
rule.before();
rule.after();
}
private static void messageOut(String msg) {
System.out.println( "\n***\tTest client helper: " + msg + "\t***\n" );
}
}