blob: 0f63701914c3a0ad1975c3e9ce0d0924c1545d7c [file] [log] [blame]
/*
* 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 static org.apache.geode.test.dunit.internal.DUnitLauncher.NUM_VMS;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.List;
import java.util.concurrent.Callable;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.logging.log4j.Logger;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.internal.process.ProcessUtils;
import org.apache.geode.test.dunit.internal.ChildVMLauncher;
import org.apache.geode.test.dunit.internal.MethodInvokerResult;
import org.apache.geode.test.dunit.internal.ProcessHolder;
import org.apache.geode.test.dunit.internal.RemoteDUnitVMIF;
import org.apache.geode.test.dunit.internal.StandAloneDUnitEnv;
import org.apache.geode.test.dunit.internal.VMEventNotifier;
import org.apache.geode.test.version.VersionManager;
/**
* This class represents a Java Virtual Machine that runs in a DistributedTest.
*/
@SuppressWarnings("serial,unused")
public class VM implements Serializable {
private static final Logger logger = LogService.getLogger();
public static final int CONTROLLER_VM = -1;
public static final int DEFAULT_VM_COUNT = NUM_VMS;
/** The host on which this VM runs */
private final Host host;
/** The sequential id of this VM */
private int id;
/** 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;
private transient volatile ProcessHolder processHolder;
private final transient ChildVMLauncher childVMLauncher;
/**
* 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 true if executed from the main JUnit VM.
*/
public static boolean isControllerVM() {
return getCurrentVMNum() == CONTROLLER_VM;
}
/**
* Returns true if executed from a DUnit VM. Returns false if executed from the main JUnit VM.
*/
public static boolean isVM() {
return getCurrentVMNum() != CONTROLLER_VM;
}
/**
* Returns a VM that runs in this DistributedTest.
*
* @param whichVM A zero-based identifier of the VM
*/
public static VM getVM(int whichVM) {
return Host.getHost(0).getVM(whichVM);
}
/**
* Returns a collection of all DistributedTest VMs.
*/
public static List<VM> getAllVMs() {
return Host.getHost(0).getAllVMs();
}
/**
* Returns the number of VMs that run in this DistributedTest.
*/
public static int getVMCount() {
return Host.getHost(0).getVMCount();
}
/**
* Returns the DistributedTest Locator VM.
*/
public static VM getLocator() {
return Host.getLocator();
}
/**
* Returns the DistributedTest Locator VM.
*/
public static VM getController() {
return getVM(CONTROLLER_VM);
}
/**
* Returns the machine name hosting this DistributedTest.
*/
public static String getHostName() {
return Host.getHost(0).getHostName();
}
/**
* Returns the name of a VM for use in the RMI naming service or working directory on disk
*/
public static String getVMName(final String version, final int pid) {
if (pid == -2) {
return "locator";
}
if (pid < 0 || VersionManager.isCurrentVersion(version)) {
return "vm" + pid;
} else {
return "vm" + pid + "_v" + version;
}
}
/**
* Returns an array of all provided VMs.
*/
public static VM[] toArray(VM... vms) {
return vms;
}
/**
* Returns an array of all provided VMs.
*/
public static VM[] toArray(List<VM> vmList) {
return vmList.toArray(new VM[0]);
}
/**
* Returns an array of all provided VMs.
*/
public static VM[] toArray(List<VM> vmList, VM... vms) {
return ArrayUtils.addAll(vmList.toArray(new VM[0]), vms);
}
/**
* Returns an array of all provided VMs.
*/
public static VM[] toArray(VM[] vmArray, VM... vms) {
return ArrayUtils.addAll(vmArray, vms);
}
/**
* Registers a {@link VMEventListener}.
*/
public static void addVMEventListener(final VMEventListener listener) {
getVMEventNotifier().addVMEventListener(listener);
}
/**
* Deregisters a {@link VMEventListener}.
*/
public static void removeVMEventListener(final VMEventListener listener) {
getVMEventNotifier().removeVMEventListener(listener);
}
private static VMEventNotifier getVMEventNotifier() {
return Host.getHost(0).getVMEventNotifier();
}
/**
* Creates a new {@code VM} that runs on a given host with a given process id.
*/
public VM(final Host host, final int id, final RemoteDUnitVMIF client,
final ProcessHolder processHolder, final ChildVMLauncher childVMLauncher) {
this(host, VersionManager.CURRENT_VERSION, id, client, processHolder, childVMLauncher);
}
public VM(final Host host, final String version, final int id, final RemoteDUnitVMIF client,
final ProcessHolder processHolder, final ChildVMLauncher childVMLauncher) {
this.host = host;
this.id = id;
this.version = version;
this.client = client;
this.processHolder = processHolder;
this.childVMLauncher = childVMLauncher;
available = true;
}
/**
* Returns the {@code Host} on which this {@code VM} is running.
*/
public Host getHost() {
return host;
}
/**
* Returns the version of Geode used in this VM.
*
* @see VersionManager#CURRENT_VERSION
* @see Host#getVM(String, int)
*/
public String getVersion() {
return version;
}
/**
* Returns the VM id of this {@code VM}.
*/
public int getId() {
return id;
}
/**
* Returns the process id of this {@code VM}.
*/
public int getPid() {
return invoke(() -> ProcessUtils.identifyPid());
}
/**
* 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
*/
@Deprecated
public <V> V invoke(final Class<?> targetClass, final String methodName) {
checkAvailability(targetClass.getName(), methodName);
return executeMethodOnClass(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
*/
@Deprecated
public <V> AsyncInvocation<V> 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
*/
@Deprecated
public <V> V invoke(final Class<?> targetClass, final String methodName, final Object[] args) {
checkAvailability(targetClass.getName(), methodName);
return executeMethodOnClass(targetClass, methodName, args);
}
/**
* 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
*/
@Deprecated
public <V> AsyncInvocation<V> invokeAsync(final Object targetObject, final String methodName,
final Object[] args) {
return new AsyncInvocation<V>(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
*/
@Deprecated
public <V> AsyncInvocation<V> invokeAsync(final Class<?> targetClass, final String methodName,
final Object[] args) {
return new AsyncInvocation<V>(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 <V> AsyncInvocation<V> 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 <V> AsyncInvocation<V> 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 <V> AsyncInvocation<V> invokeAsync(final String name,
final SerializableCallableIF<V> 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 <V> AsyncInvocation<V> invokeAsync(final SerializableCallableIF<V> 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) {
checkAvailability(NamedRunnable.class.getName(), "run");
executeMethodOnObject(new NamedRunnable(name, runnable), "run", 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
*
* @see SerializableRunnable
*/
public void invoke(final SerializableRunnableIF runnable) {
checkAvailability(runnable.getClass().getName(), "run");
executeMethodOnObject(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 <V> V invoke(final String name, final SerializableCallableIF<V> callable) {
checkAvailability(NamedCallable.class.getName(), "call");
return executeMethodOnObject(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 <V> V invoke(final SerializableCallableIF<V> callable) {
checkAvailability(callable.getClass().getName(), "call");
return executeMethodOnObject(callable, "call", new Object[0]);
}
/**
* 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.
*/
@Deprecated
public <V> V invoke(final Object targetObject, final String methodName) {
checkAvailability(targetObject.getClass().getName(), methodName);
return executeMethodOnObject(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.
*/
@Deprecated
public <V> V invoke(final Object targetObject, final String methodName, final Object[] args) {
checkAvailability(targetObject.getClass().getName(), methodName);
return executeMethodOnObject(targetObject, methodName, args);
}
/**
* Restart an unavailable VM
*/
public synchronized void makeAvailable() {
if (!available) {
available = true;
bounce();
}
}
/**
* Synchronously bounces (nice kill 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.
*
* Note: bounce invokes shutdown hooks.
*
* @throws RMIException if an exception occurs while bouncing this {@code VM}
*/
public void bounce() {
bounce(version, false);
}
/**
* Synchronously bounces (forced kill 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.
*
* Note: Forced bounce does not invoke shutdown hooks.
*
* @throws RMIException if an exception occurs while bouncing this {@code VM}
*/
public void bounceForcibly() {
bounce(version, true);
}
public void bounce(final String targetVersion) {
bounce(targetVersion, false);
}
private synchronized void bounce(final String targetVersion, boolean force) {
checkAvailability(getClass().getName(), "bounceVM");
logger.info("Bouncing {} old pid is {}", id, getPid());
getVMEventNotifier().notifyBeforeBounceVM(this);
available = false;
try {
if (force) {
processHolder.killForcibly();
} else {
SerializableRunnableIF runnable = () -> new Thread(() -> {
try {
// sleep before exit so that the rmi call is returned
Thread.sleep(100);
System.exit(0);
} catch (InterruptedException e) {
logger.error("VM bounce thread interrupted before exiting.", e);
}
}).start();
executeMethodOnObject(runnable, "run", new Object[0]);
}
processHolder.waitFor();
processHolder = childVMLauncher.launchVM(targetVersion, id, true);
version = targetVersion;
client = childVMLauncher.getStub(id);
available = true;
logger.info("Bounced {} new pid is {}", id, getPid());
getVMEventNotifier().notifyAfterBounceVM(this);
} catch (InterruptedException | IOException | NotBoundException e) {
throw new Error("Unable to restart VM " + id, e);
}
}
private void checkAvailability(String className, String methodName) {
if (!available) {
throw new RMIException(this, className, methodName,
new IllegalStateException("VM not available: " + this));
}
}
public File getWorkingDirectory() {
return DUnitEnv.get().getWorkingDirectory(getVersion(), getId());
}
@Override
public String toString() {
return "VM " + getId() + " running on " + getHost()
+ (VersionManager.isCurrentVersion(version) ? "" : (" with version " + version));
}
private <V> V executeMethodOnObject(final Object targetObject, final String methodName,
final Object[] args) {
try {
MethodInvokerResult result = client.executeMethodOnObject(targetObject, methodName, args);
if (result.exceptionOccurred()) {
throw new RMIException(this, targetObject.getClass().getName(), methodName,
result.getException(), result.getStackTrace());
}
return (V) result.getResult();
} catch (RemoteException exception) {
throw new RMIException(this, targetObject.getClass().getName(), methodName, exception);
}
}
private <V> V executeMethodOnClass(final Class<?> targetClass, final String methodName,
final Object[] args) {
try {
MethodInvokerResult result =
client.executeMethodOnClass(targetClass.getName(), methodName, args);
if (result.exceptionOccurred()) {
throw new RMIException(this, targetClass.getName(), methodName, result.getException(),
result.getStackTrace());
}
return (V) result.getResult();
} catch (RemoteException exception) {
throw new RMIException(this, targetClass.getName(), methodName, exception);
}
}
}