/*
* 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.geode.test.dunit;
import java.io.File;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.rmi.RemoteException;
import java.util.concurrent.Callable;
import com.jayway.awaitility.Awaitility;
import hydra.MethExecutorResult;
import org.apache.geode.internal.process.ProcessUtils;
import org.apache.geode.test.dunit.standalone.BounceResult;
import org.apache.geode.test.dunit.standalone.RemoteDUnitVMIF;
import org.apache.geode.test.dunit.standalone.StandAloneDUnitEnv;
import org.apache.geode.test.dunit.standalone.VersionManager;
/**
* This class represents a Java Virtual Machine that runs on a host.
*/
@SuppressWarnings("serial")
public class VM implements Serializable {
/** The host on which this VM runs */
private Host host;
/** The process id of this VM */
private int pid;
/** The version of Geode used in this VM */
private String version;
/** The hydra client for this VM */
private RemoteDUnitVMIF client;
/** The state of this VM */
private volatile boolean available;
/**
* Returns the {@code VM} identity. For {@link StandAloneDUnitEnv} the number returned is a
* zero-based sequence representing the order in with the DUnit {@code VM}s were launched.
*/
public static int getCurrentVMNum() {
return DUnitEnv.get().getVMID();
}
/**
* Returns the total number of {@code VM}s on all {@code Host}s (note that DUnit currently only
* supports one {@code Host}).
*/
public static int getVMCount() {
int count = 0;
for (int h = 0; h < Host.getHostCount(); h++) {
Host host = Host.getHost(h);
count += host.getVMCount();
}
return count;
}
/**
* Returns the name of a VM for use in the RMI naming service or working directory on disk
*/
public static String getVMName(String version, int pid) {
if (pid == -2) {
return "locator";
}
if (pid < 0 || VersionManager.isCurrentVersion(version)) {
return "vm" + pid;
} else {
return "vm" + pid + "_v" + version;
}
}
/**
* Creates a new {@code VM} that runs on a given host with a given process id.
*
* TODO: change pid to reflect value from {@link ProcessUtils#identifyPid()}
*/
public VM(final Host host, final int pid, final RemoteDUnitVMIF client) {
this(host, VersionManager.CURRENT_VERSION, pid, client);
}
public VM(final Host host, final String version, final int pid, final RemoteDUnitVMIF client) {
this.host = host;
this.pid = pid;
this.version = version;
this.client = client;
this.available = true;
}
/**
* Returns the {@code Host} on which this {@code VM} is running.
*/
public Host getHost() {
return this.host;
}
/**
* Returns the version of Geode used in this VM.
*
* @see VersionManager#CURRENT_VERSION
* @see Host#getVM(String, int)
*/
public String getVersion() {
return this.version;
}
/**
* Returns the process id of this {@code VM}.
*/
public int getPid() {
return this.pid;
}
/**
* Invokes a static zero-arg method with an {@link Object} or {@code void} return type in this
* {@code VM}. If the return type of the method is {@code void}, {@code null} is returned.
*
* @param targetClass The class on which to invoke the method
* @param methodName The name of the method to invoke
*
* @throws RMIException Wraps any underlying exception thrown while invoking the method in this VM
*
* @deprecated Please use {@link #invoke(SerializableCallableIF)} instead
*/
public Object invoke(final Class targetClass, final String methodName) {
return invoke(targetClass, methodName, new Object[0]);
}
/**
* Asynchronously invokes a static zero-arg method with an {@code Object} or {@code void} return
* type in this VM. If the return type of the method is {@code void}, {@code null} is returned.
*
* @param targetClass The class on which to invoke the method
* @param methodName The name of the method to invoke
*
* @deprecated Please use {@link #invoke(SerializableCallableIF)} instead
*/
public AsyncInvocation invokeAsync(final Class targetClass, final String methodName) {
return invokeAsync(targetClass, methodName, null);
}
/**
* Invokes a static method with an {@link Object} or {@code void} return type in this VM. If the
* return type of the method is {@code void}, {@code null} is returned.
*
* @param targetClass The class on which to invoke the method
* @param methodName The name of the method to invoke
* @param args Arguments passed to the method call (must be {@link java.io.Serializable}).
*
* @throws RMIException Wraps any underlying exception thrown while invoking the method in this
* {@code VM}
*
* @deprecated Please use {@link #invoke(SerializableCallableIF)} instead
*/
public Object invoke(final Class targetClass, final String methodName, final Object[] args) {
if (!this.available) {
throw new RMIException(this, targetClass.getName(), methodName,
new IllegalStateException("VM not available: " + this));
}
MethExecutorResult result = execute(targetClass, methodName, args);
if (!result.exceptionOccurred()) {
return result.getResult();
} else {
throw new RMIException(this, targetClass.getName(), methodName, result.getException(),
result.getStackTrace());
}
}
/**
* Asynchronously invokes an instance method with an {@link Object} or {@code void} return type in
* this {@code VM}. If the return type of the method is {@code void}, {@code null} is returned.
*
* @param targetObject The object on which to invoke the method
* @param methodName The name of the method to invoke
* @param args Arguments passed to the method call (must be {@link java.io.Serializable}).
*
* @deprecated Please use {@link #invoke(SerializableCallableIF)} instead
*/
public AsyncInvocation invokeAsync(final Object targetObject, final String methodName,
final Object[] args) {
return new AsyncInvocation(targetObject, methodName,
() -> invoke(targetObject, methodName, args)).start();
}
/**
* Asynchronously invokes an instance method with an {@link Object} or {@code void} return type in
* this {@code VM}. If the return type of the method is {@code void}, {@code null} is returned.
*
* @param targetClass The class on which to invoke the method
* @param methodName The name of the method to invoke
* @param args Arguments passed to the method call (must be {@link java.io.Serializable}).
*
* @deprecated Please use {@link #invoke(SerializableCallableIF)} instead
*/
public AsyncInvocation invokeAsync(final Class<?> targetClass, final String methodName,
final Object[] args) {
return new AsyncInvocation(targetClass, methodName, () -> invoke(targetClass, methodName, args))
.start();
}
/**
* Invokes the {@code run} method of a {@link Runnable} in this VM. Recall that {@code run} takes
* no arguments and has no return value.
*
* @param runnable The {@code Runnable} to be run
*
* @see SerializableRunnable
*/
public AsyncInvocation invokeAsync(final SerializableRunnableIF runnable) {
return invokeAsync(runnable, "run", new Object[0]);
}
/**
* Invokes the {@code run} method of a {@link Runnable} in this VM. Recall that {@code run} takes
* no arguments and has no return value. The {@code Runnable} is wrapped in a
* {@link NamedRunnable} having the given name so it shows up in DUnit logs.
*
* @param runnable The {@code Runnable} to be run
* @param name The name of the {@code Runnable}, which will be logged in DUnit output
*
* @see SerializableRunnable
*/
public AsyncInvocation invokeAsync(final String name, final SerializableRunnableIF runnable) {
return invokeAsync(new NamedRunnable(name, runnable), "run", new Object[0]);
}
/**
* Invokes the {@code call} method of a {@link Callable} in this {@code VM}.
*
* @param callable The {@code Callable} to be run
* @param name The name of the {@code Callable}, which will be logged in dunit output
*
* @see SerializableCallable
*/
public <T> AsyncInvocation<T> invokeAsync(final String name,
final SerializableCallableIF<T> callable) {
return invokeAsync(new NamedCallable(name, callable), "call", new Object[0]);
}
/**
* Invokes the {@code call} method of a {@link Callable} in this {@code VM}.
*
* @param callable The {@code Callable} to be run
*
* @see SerializableCallable
*/
public <T> AsyncInvocation<T> invokeAsync(final SerializableCallableIF<T> callable) {
return invokeAsync(callable, "call", new Object[0]);
}
/**
* Invokes the {@code run} method of a {@link Runnable} in this {@code VM}. Recall that
* {@code run} takes no arguments and has no return value.
*
* @param runnable The {@code Runnable} to be run
* @param name The name of the {@code Runnable}, which will be logged in DUnit output
*
* @see SerializableRunnable
*/
public void invoke(final String name, final SerializableRunnableIF runnable) {
invoke(new NamedRunnable(name, runnable), "run");
}
/**
* Invokes the {@code run} method of a {@link Runnable} in this {@code VM}. Recall that
* {@code run} takes no arguments and has no return value.
*
* @param runnable The {@code Runnable} to be run
*
* @see SerializableRunnable
*/
public void invoke(final SerializableRunnableIF runnable) {
invoke(runnable, "run");
}
/**
* Invokes the {@code call} method of a {@link Callable} in this {@code VM}.
*
* @param callable The {@code Callable} to be run
* @param name The name of the {@code Callable}, which will be logged in DUnit output
*
* @see SerializableCallable
*/
public <T> T invoke(final String name, final SerializableCallableIF<T> callable) {
return (T) invoke(new NamedCallable(name, callable), "call");
}
/**
* Invokes the {@code call} method of a {@link Callable} in this {@code VM}.
*
* @param callable The {@code Callable} to be run
*
* @see SerializableCallable
*/
public <T> T invoke(final SerializableCallableIF<T> callable) {
return (T) invoke(callable, "call");
}
/**
* Invokes the {@code run} method of a {@link Runnable} in this {@code VM}. If the invocation
* throws AssertionError, and repeatTimeoutMs is >0, the {@code run} method is invoked repeatedly
* until it either succeeds, or repeatTimeoutMs has passed. The AssertionError is thrown back to
* the sender of this method if {@code run} has not completed successfully before repeatTimeoutMs
* has passed.
*
* @deprecated Please use {@link Awaitility} to await condition and then
* {@link #invoke(SerializableCallableIF)} instead.
*/
public void invokeRepeatingIfNecessary(final RepeatableRunnable runnable,
final long repeatTimeoutMs) {
invoke(runnable, "runRepeatingIfNecessary", new Object[] {repeatTimeoutMs});
}
/**
* Invokes an instance method with no arguments on an object that is serialized into this
* {@code VM}. The return type of the method can be either {@link Object} or {@code void}. If the
* return type of the method is {@code void}, {@code null} is returned.
*
* @param targetObject The receiver of the method invocation
* @param methodName The name of the method to invoke
*
* @throws RMIException Wraps any underlying exception thrown while invoking the method in this
* {@code VM}
*
* @deprecated Please use {@link #invoke(SerializableCallableIF)} instead.
*/
public Object invoke(final Object targetObject, final String methodName) {
return invoke(targetObject, methodName, new Object[0]);
}
/**
* Invokes an instance method on an object that is serialized into this {@code VM}. The return
* type of the method can be either {@link Object} or {@code void}. If the return type of the
* method is {@code void}, {@code null} is returned.
*
* @param targetObject The receiver of the method invocation
* @param methodName The name of the method to invoke
* @param args Arguments passed to the method call (must be {@link java.io.Serializable}).
*
* @throws RMIException Wraps any underlying exception thrown while invoking the method in this
* {@code VM}
*
* @deprecated Please use {@link #invoke(SerializableCallableIF)} instead.
*/
public Object invoke(final Object targetObject, final String methodName, final Object[] args) {
if (!this.available) {
throw new RMIException(this, targetObject.getClass().getName(), methodName,
new IllegalStateException("VM not available: " + this));
}
MethExecutorResult result = execute(targetObject, methodName, args);
if (!result.exceptionOccurred()) {
return result.getResult();
} else {
throw new RMIException(this, targetObject.getClass().getName(), methodName,
result.getException(), result.getStackTrace());
}
}
/**
* Synchronously bounces (mean kills and restarts) this {@code VM}. Concurrent bounce attempts are
* synchronized but attempts to invoke methods on a bouncing {@code VM} will cause test failure.
* Tests using bounce should be placed at the end of the DUnit test suite, since an exception here
* will cause all tests using the unsuccessfully bounced {@code VM} to fail.
*
* This method is currently not supported by the standalone DUnit runner.
*
* @throws RMIException if an exception occurs while bouncing this {@code VM}, for example a
* {@code HydraTimeoutException} if the {@code VM} fails to stop within
* {@code hydra.Prms#maxClientShutdownWaitSec} or restart within
* {@code hydra.Prms#maxClientStartupWaitSec}.
*/
public synchronized void bounce() {
bounce(this.version);
}
public synchronized void bounce(String targetVersion) {
if (!this.available) {
throw new RMIException(this, getClass().getName(), "bounceVM",
new IllegalStateException("VM not available: " + this));
}
this.available = false;
try {
BounceResult result = DUnitEnv.get().bounce(targetVersion, this.pid);
this.pid = result.getNewPid();
this.client = result.getNewClient();
this.version = targetVersion;
this.available = true;
} catch (UnsupportedOperationException e) {
this.available = true;
throw e;
} catch (RemoteException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw, true));
RMIException rmie =
new RMIException(this, getClass().getName(), "bounceVM", e, sw.toString());
throw rmie;
}
}
public String toString() {
return "VM " + getPid() + " running on " + getHost()
+ (VersionManager.isCurrentVersion(version) ? "" : (" with version " + version));
}
public File getWorkingDirectory() {
return DUnitEnv.get().getWorkingDirectory(getVersion(), getPid());
}
private MethExecutorResult execute(final Class targetClass, final String methodName,
final Object[] args) {
try {
return this.client.executeMethodOnClass(targetClass.getName(), methodName, args);
} catch (RemoteException exception) {
throw new RMIException(this, targetClass.getName(), methodName, exception);
}
}
private MethExecutorResult execute(final Object targetObject, final String methodName,
final Object[] args) {
try {
if (args == null) {
return this.client.executeMethodOnObject(targetObject, methodName);
} else {
return this.client.executeMethodOnObject(targetObject, methodName, args);
}
} catch (RemoteException exception) {
throw new RMIException(this, targetObject.getClass().getName(), methodName, exception);
}
}
}